Разработка API-оболочки C для объектно-ориентированного кода C ++ - PullRequest
73 голосов
/ 12 января 2010

Я хочу разработать набор API C, который будет оборачиваться вокруг наших существующих API C ++ для доступа к нашей основной логике (написанной на объектно-ориентированном C ++). По сути, это будет связующий API, который позволит использовать нашу логику C ++ в других языках. Какие есть хорошие учебные пособия, книги или передовые практики, которые знакомят с концепциями обертывания C вокруг объектно-ориентированного C ++?

Ответы [ 6 ]

63 голосов
/ 12 января 2010

Это не так сложно сделать вручную, но будет зависеть от размера вашего интерфейса. В тех случаях, когда я делал это, разрешалось использовать нашу библиотеку C ++ из чистого кода C, и, таким образом, SWIG не сильно помог. (Ну, может быть, SWIG можно использовать для этого, но я не гуру SWIG, и это казалось нетривиальным)

Все, что мы в итоге сделали, было:

  1. Каждый объект передается в C непрозрачной ручкой.
  2. Конструкторы и деструкторы заключены в чистые функции
  3. Функции-члены являются чистыми функциями.
  4. Другие встроенные функции отображаются в эквиваленты 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 ++.

14 голосов
/ 12 января 2010

Я думаю, что ответ Майкла Андерсона на правильном пути, но мой подход был бы другим. Вы должны беспокоиться об одной дополнительной вещи: исключения. Исключения не являются частью 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 намного безопаснее.

Но, как упоминал Майкл, связывание может быть довольно сложным.

Надеюсь, это поможет

9 голосов
/ 12 января 2010

Нетрудно представить код 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

6 голосов
/ 12 января 2010

Я думаю, вы сможете получить некоторые идеи о направлении и / или, возможно, использовать напрямую 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 может свободно использоваться, распространяться и модифицироваться для коммерческого и некоммерческого использования.

4 голосов
/ 12 января 2010

Просто замените концепцию объекта на void * (часто называемый непрозрачным типом в C-ориентированных библиотеках) и используйте все, что вы знаете из C ++.

2 голосов
/ 11 мая 2015

Я думаю, что использование SWIG - лучший ответ ... он не только избегает переизобретения колеса, но и надежен, а также способствует непрерывности развития, а не одному решению проблемы.

Высокочастотные проблемы необходимо решать путем долгосрочного решения.

...