Разработка API-оболочки C для объектно-ориентированного кода C++

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

6 ответов

Решение

Это не слишком сложно сделать вручную, но будет зависеть от размера вашего интерфейса. Случаи, когда я делал это, состояли в том, чтобы разрешить использование нашей библиотеки 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++.

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

Проблемы высокой частоты должны решаться путем долгосрочного решения.

Другие вопросы по тегам