Я пытаюсь обернуть существующую стороннюю библиотеку C++ в интерфейс C, чтобы ее можно было использовать в привязках для другого языка. У меня возникли проблемы с выяснением того, как обернуть переименованное пространство имен, а не просто переопределить его:
// Existing C++ 3rd party library header
namespace foo {
enum Fruit {
APPLE = 0,
ORANGE
}
}
Итак, у меня есть мой завернутый. {H, cpp} с extern "C"
блоком extern "C"
, и я просто не могу понять, как экспортировать foo::Fruit
в интерфейс C
// wrapped.h
#ifdef __cplusplus
extern "C" {
#endif
// I don't want to do this
typedef enum Fruit {
APPLE = 0,
ORANGE
} Fruit;
#ifdef __cplusplus
}
#endif
#endif
Можно ли экспортировать (зеркало) foo::Fruit
из библиотеки C++ в мою обертку C как Fruit
?
edit: Я просто заметил, что вы хотите обернуть существующую библиотеку, не изменяя ее.
Тогда я боюсь, что тебе повезло. В общем, просто невозможно извлечь только членов перечисления из кода C++ без укуса компилятора C.
На практике у вас есть выбор: программно перевести свой собственный набор перечислений в версии C++ в интерфейсе, попытайтесь точно зеркально отобразить C++ и поместите кучу статических утверждений для двойной проверки или теоретически даже отфильтровывая их через скрипты.
Боюсь, здесь нет никаких хороших вариантов. Для записи я бы предпочел первый из этих плохих вариантов.
Тем не менее, если требуется, и число констант велико, вы можете сделать немного макромагии, чтобы получить одно определение с "пространствами имен" в стиле C, по мере необходимости.
Сначала один заголовок, определяющий все записи перечисления через макрос:
/* Fruit.h */
FOO_ENUM(APPLE) = 0,
FOO_ENUM(ORANGE)
Затем в заголовке C:
/* C interface */
typedef enum {
# define FOO_ENUM(id) FOO_##id
# include "Fruit.h"
# undef FOO_ENUM
} Foo_Fruit_t;
И, наконец, в заголовке C++:
// C++ interface
namespace Foo {
enum Fruit_t {
# define FOO_ENUM(id) id
# include "Fruit.h"
# undef FOO_ENUM
};
}
Конечно, есть много альтернатив. Например, если вы не против загрязнять глобальное пространство имен в C++, то всегда можете определить полное перечисление непосредственно в интерфейсе C и скопировать отдельные члены перечисления в C++ версию определения.
Fruit.inc
было бы использовать Fruit.inc
: это не заголовочный файл, но он должен быть #include
'd. В противном случае, это совершенно нормальное (в конце концов, это взаимодействие C / C ++).
typedef
которое псевдоним foo::Fruit
to Fruit
. Ну что ж. В случае, если это вообще имеет значение для вашего ответа, в частности, я обертываю эту библиотеку , чтобы я мог создавать привязки для языка Go
.
Я столкнулся с этой конкретной проблемой с enums
в C-обертке для библиотеки C++ в последнее время и вызвал довольно большую головную боль.
Мое решение показано в основном в основном минимальном рабочем примере, но оно ужасно неэлегантно. Это, по сути, переводный подход.
Нужно быть осторожным, чтобы ничего не объявлять дважды в отношении enums
. Пример передает int
, string
или массив char
и enum
.
Заголовок библиотеки, написанный в C++. Это библиотека, которая будет завернута. MyClass.h:
#ifndef __MYCLASS_H
#define __MYCLASS_H
#include <iostream>
namespace MyNamespace {
using namespace std;
enum EnumControlInterface {HIDController=1, UVCController=2};
class MyClass {
private:
int m_i;
string m_text;
EnumControlInterface _controller;
public:
MyClass(int val);
~MyClass();
void int_set(int i);
void string_set(string text);
int int_get();
string string_get();
void writeEnum(EnumControlInterface MyInterface);
EnumControlInterface readEnum();
};
};
#endif
C++ реализация MyClass.cpp:
#include "MyClass.h"
namespace MyNamespace {
MyClass::MyClass(int val) {
cout << "MyClass is being created" << endl;
cout << "The parameter passed to the MyClass constructor is: " << val << endl;
}
MyClass::~MyClass() {
cout << "MyClass is being destroyed" << endl;
}
void MyClass::writeEnum(EnumControlInterface MyInterface) {
_controller = MyInterface;
cout << "The interface control Enum is set in MyClass.cpp as: " << _controller << endl;
}
EnumControlInterface MyClass::readEnum() {
return _controller;
}
void MyClass::string_set(std::string text) {
m_text = text;
}
string MyClass::string_get() {
return m_text;
}
void MyClass::int_set(int i) {
m_i = i;
}
int MyClass::int_get() {
return m_i;
}
}
Заголовочный файл "C wrapper" MyWrapper.h, который завершает MyClass.h:
#ifndef __MYWRAPPER_H
#define __MYWRAPPER_H
#ifdef __cplusplus
namespace MyNamespace {
extern "C" {
#endif
typedef enum WrapperEnumControlInterface {WrapHIDController=1, WrapUVCController=2} WrapperEnumControlInterface;
typedef struct MyClass MyClass;
MyClass* newMyClass(int val);
void MyClass_int_set(MyClass* v, int i);
int MyClass_int_get(MyClass* v);
void MyClass_string_set(MyClass* v, char* text);
char* MyClass_string_get(MyClass* v);
void MyClass_writeEnum(MyClass* v, WrapperEnumControlInterface MyInterface);
WrapperEnumControlInterface MyClass_readEnum(MyClass* v);
void deleteMyClass(MyClass* v);
#ifdef __cplusplus
}
}
#endif
#endif
Реализация "C wrapper" написана в виде смеси C и C++. В частности, определения функций должны быть C, а переданные и возвращаемые параметры также должны быть типами C. Внутри функций и внутри областей препроцессора __cplusplus
C или C++ должно быть хорошо.
Например, нельзя запросить функцию внутри блока extern "C"
чтобы принять тип std::string
. Это приведет к поражению цели оболочки: выставить только код C, который управляет базовой библиотекой C++. extern "C"
определяет, что отображается без искажения имени (см. вопросы об изменении имени в C++). __cplusplus
определяется (многими) C++ компиляторами.
MyWrapper.cc:
#include "MyClass.h"
#include "MyWrapper.h"
#include <vector>
namespace MyNamespace {
extern "C" {
MyClass* newMyClass(int val) {
return new MyClass(val);
}
void deleteMyClass(MyClass* v) {
delete v;
}
void MyClass_int_set(MyClass* v, int i) {
v->int_set(i);
}
int MyClass_int_get(MyClass* v) {
return v->int_get();
}
void MyClass_string_set(MyClass* v, char* text) {
//convert incomming C char* to a C++ string
string stringToSend = string(text);
cout << "the string received from the program by the wrapper is " << text << endl;
cout << "the string sent to the library by the wrapper is " << stringToSend << endl;
v->string_set(stringToSend);
}
char* MyClass_string_get(MyClass* v) {
string result = v->string_get();
cout << "the string received from the library by the wrapper is " << result << endl;
// Convert the C++ string result to a C char pointer and return it. Use vectors to do the memory management.
// A vector type of as many chars as necessary to hold the result string
static vector<char> resultVector(result.begin(), result.end());
cout << "the data in the vector who pointer is returned to the program by the wrapper is: " << &resultVector[0] << endl;
return (&resultVector[0]);
}
void MyClass_writeEnum(MyClass* v, WrapperEnumControlInterface MyInterface) {
v->writeEnum((EnumControlInterface)MyInterface);
}
WrapperEnumControlInterface MyClass_readEnum(MyClass* v) {
EnumControlInterface result = v->readEnum();
return (WrapperEnumControlInterface)result;
}
}
}
AC, которая вызывает библиотеку C++ через обертку Cproject.c:
#include "MyWrapper.h"
#include "stdio.h"
int main(int argc, char* argv[]) {
struct MyClass* clsptr = newMyClass(5);
MyClass_int_set(clsptr, 3);
printf("The int read back in Cproject.c is: %i\n", MyClass_int_get(clsptr));
MyClass_writeEnum(clsptr, WrapUVCController);
printf("The enum read back in Cproject.c is: %d\n", MyClass_readEnum(clsptr));
MyClass_string_set(clsptr, "Hello");
char *textReadBack = MyClass_string_get(clsptr);
printf("The text read back in Cproject.c is: %s \n", textReadBack);
deleteMyClass(clsptr);
return 0;
}
И только для полноты проекта C++, который вызывает библиотеку C++ напрямую, без использования обертки CPPProgram.cpp, так коротко !:
#include "MyClass.h"
#include <iostream>
using namespace std;
using namespace MyNamespace;
int main(int argc, char* argv[]) {
MyClass *c = new MyClass(42);
c->int_set(3);
cout << c->int_get() << endl;
c->writeEnum(HIDController);
cout << c->readEnum() << endl;
c->string_set("Hello");
cout << c->string_get() << endl;
delete c;
}
Класс MyClass C++ скомпилирован в статическую библиотеку, оболочка скомпилирована в общую библиотеку, нет особой причины, оба могут быть статическими или совместно используемыми.
Программа C, вызывающая библиотеку обертки (Cproject.c), должна быть связана с компилятором C++ (G++ и т.д.),
Очевидно, что этот пример не имеет серьезного приложения. Он основан на https://www.teddy.ch/C++_library_in_c/ в терминах структуры, но с добавленными битами enum
.
Часто человек, который пишет обертку, не имеет доступа к исходному коду библиотеки, которую они пытаются обернуть (в этом случае MyClass.cpp), они будут иметь.so или.dll или.a или.lib для Linux и Соответственно, общие и статические библиотеки Windows. Нет необходимости иметь исходный код для библиотеки C++. Для создания эффективной обертки необходимы только файлы заголовков для библиотеки C++.
Я частично написал это, чтобы предоставить более подробный ответ на исходный вопрос: тот, который можно легко скопировать и скомпилировать, но также потому, что это единственный способ, которым я смог решить проблему до сих пор, и это не удовлетворительно, на мой взгляд. Я хотел бы иметь возможность обертывать enums
же, как один обертывает функции публичного члена, а не повторно создавать enums
внутри обертки с немного разными именами.
Источники связанной информации, которые оказались полезными:
https://www.teddy.ch/C++_library_in_c/
Как передать/присвоить одно значение enum другому перечислению
Разработка API-интерфейса оболочки для объектно-ориентированного кода C++
Преобразование строки в стиле C в строку C++ std ::
Возвращающий указатель от функции
Конечно, все небезопасные, неправильные и т.д. Методы кодирования - это моя ошибка полностью.
enums
, что некоторые из них означают, что Я не знаю.
extern "C"
потому что я не могу ссылаться на исходное перечисление пространства имен в сторонней библиотеке. Есть ли решение для макроса?