Разработка API-оболочки C для объектно-ориентированного кода C++
Я рассчитываю разработать набор API C, который будет охватывать наши существующие API C++ для доступа к нашей основной логике (написанной на объектно-ориентированном C++). По сути, это будет связующий API, который позволит использовать нашу логику C++ в других языках. Какие есть хорошие учебные пособия, книги или передовые практики, которые знакомят с концепциями обертывания C вокруг объектно-ориентированного C++?
6 ответов
Это не слишком сложно сделать вручную, но будет зависеть от размера вашего интерфейса. Случаи, когда я делал это, состояли в том, чтобы разрешить использование нашей библиотеки C++ из чистого кода C, и, таким образом, SWIG не сильно помог. (Ну, может быть, SWIG может быть использован для этого, но я не гуру SWIG, и это казалось нетривиальным)
Все, что мы в итоге сделали:
- Каждый объект передается в C непрозрачным дескриптором.
- Конструкторы и деструкторы обернуты в чистые функции
- Функции-члены являются чистыми функциями.
- Другие встроенные функции отображаются в C-эквивалентах, где это возможно.
Итак, такой класс (заголовок C++)
class MyClass
{
public:
explicit MyClass( std::string & s );
~MyClass();
int doSomething( int j );
}
Будет сопоставлен с интерфейсом C, как это (заголовок C):
struct HMyClass; // An opaque type that we'll use as a handle
typedef struct HMyClass HMyClass;
HMyClass * myStruct_create( const char * s );
void myStruct_destroy( HMyClass * v );
int myStruct_doSomething( HMyClass * v, int i );
Реализация интерфейса будет выглядеть так (источник C++)
#include "MyClass.h"
extern "C"
{
HMyClass * myStruct_create( const char * s )
{
return reinterpret_cast<HMyClass*>( new MyClass( s ) );
}
void myStruct_destroy( HMyClass * v )
{
delete reinterpret_cast<MyClass*>(v);
}
int myStruct_doSomething( HMyClass * v, int i )
{
return reinterpret_cast<MyClass*>(v)->doSomething(i);
}
}
Мы извлекаем наш непрозрачный дескриптор из исходного класса, чтобы избежать необходимости приведения, и(похоже, это не работает с моим текущим компилятором). Мы должны сделать дескриптор структурой, так как C не поддерживает классы.
Так что это дает нам базовый интерфейс C. Если вам нужен более полный пример, показывающий, как можно интегрировать обработку исключений, попробуйте мой код на github: https://gist.github.com/mikeando/5394166
Самое интересное - теперь убедиться, что все необходимые библиотеки C++ правильно связаны в большую библиотеку. Для gcc (или clang) это означает, что нужно просто выполнить финальную стадию соединения с использованием g++.
Я думаю, что ответ Майкла Андерсона находится на правильном пути, но мой подход был бы другим. Вы должны беспокоиться об одной дополнительной вещи: исключения. Исключения не являются частью C ABI, поэтому вы не можете позволить исключениям когда-либо выбрасываться за пределы кода C++. Итак, ваш заголовок будет выглядеть так:
#ifdef __cplusplus
extern "C"
{
#endif
void * myStruct_create( const char * s );
void myStruct_destroy( void * v );
int myStruct_doSomething( void * v, int i );
#ifdef __cplusplus
}
#endif
И файл.cpp вашей оболочки будет выглядеть так:
void * myStruct_create( const char * s ) {
MyStruct * ms = NULL;
try { /* The constructor for std::string may throw */
ms = new MyStruct(s);
} catch (...) {}
return static_cast<void*>( ms );
}
void myStruct_destroy( void * v ) {
MyStruct * ms = static_cast<MyStruct*>(v);
delete ms;
}
int myStruct_doSomething( void * v, int i ) {
MyStruct * ms = static_cast<MyStruct*>(v);
int ret_value = -1; /* Assuming that a negative value means error */
try {
ret_value = ms->doSomething(i);
} catch (...) {}
return ret_value;
}
Еще лучше: если вы знаете, что все, что вам нужно в качестве отдельного экземпляра MyStruct, не рискуйте работать с пустыми указателями, передаваемыми вашему API. Сделайте что-то вроде этого:
static MyStruct * _ms = NULL;
int myStruct_create( const char * s ) {
int ret_value = -1; /* error */
try { /* The constructor for std::string may throw */
_ms = new MyStruct(s);
ret_value = 0; /* success */
} catch (...) {}
return ret_value;
}
void myStruct_destroy() {
if (_ms != NULL) {
delete _ms;
}
}
int myStruct_doSomething( int i ) {
int ret_value = -1; /* Assuming that a negative value means error */
if (_ms != NULL) {
try {
ret_value = _ms->doSomething(i);
} catch (...) {}
}
return ret_value;
}
Этот API намного безопаснее.
Но, как упоминал Майкл, связывание может быть довольно сложным.
Надеюсь это поможет
Нетрудно представить код C++ для C, просто используйте шаблон проектирования Facade.
Я предполагаю, что ваш код C++ встроен в библиотеку, все, что вам нужно сделать, это сделать один модуль C из вашей библиотеки C++ в качестве Фасада для вашей библиотеки вместе с чистым заголовочным файлом C. Модуль C будет вызывать соответствующие функции C++
Как только вы это сделаете, ваши приложения и библиотека C получат полный доступ к C api, который вы раскрыли.
например, вот пример модуля Фасад
#include <libInterface.h>
#include <objectedOrientedCppStuff.h>
int doObjectOrientedStuff(int *arg1, int arg2, char *arg3) {
Object obj = ObjectFactory->makeCppObj(arg3); // doing object oriented stuff here
obj->doStuff(arg2);
return obj->doMoreStuff(arg1);
}
Затем вы выставляете эту функцию C как свой API и можете свободно использовать ее как библиотеку C, не беспокоясь о
// file name "libIntrface.h"
extern int doObjectOrientedStuff(int *, int, char*);
Очевидно, что это надуманный пример, но это самый простой способ показать C++ библиотеку C
Я думаю, что вы можете получить некоторые идеи о направлении и / или, возможно, использовать непосредственно SWIG. Я думаю, что просмотр нескольких примеров, по крайней мере, даст вам представление о том, какие вещи следует учитывать при переносе одного API в другой. Упражнение может быть полезным.
SWIG - это инструмент разработки программного обеспечения, который связывает программы, написанные на C и C++, с различными языками программирования высокого уровня. SWIG используется с различными типами языков, включая распространенные языки сценариев, такие как Perl, PHP, Python, Tcl и Ruby. В список поддерживаемых языков также входят языки, не относящиеся к сценариям, такие как C#, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), Java, Lua, Modula-3, OCAML, Octave и R. Также несколько интерпретированных и скомпилированных реализаций Scheme (Guile, MzScheme, Chicken) поддерживаются. SWIG чаще всего используется для создания интерпретируемых или скомпилированных сред программирования высокого уровня, пользовательских интерфейсов и в качестве инструмента для тестирования и создания прототипов программного обеспечения C/C++. SWIG также может экспортировать свое дерево разбора в виде XML-выражений и s-выражений Lisp. SWIG может свободно использоваться, распространяться и модифицироваться для коммерческого и некоммерческого использования.
Просто замените понятие объекта на void *
(часто упоминается как непрозрачный тип в C-ориентированных библиотеках) и повторно использует все, что вы знаете из C++.
Я думаю, что использование SWIG - лучший ответ... он не только избегает повторного изобретения колеса, но и надежен, а также способствует преемственности в разработке, а не устранению проблемы.
Проблемы высокой частоты должны решаться путем долгосрочного решения.