Розробка API обгортки C для об’єктно-орієнтованого коду C ++


81

Я прагну розробити набір API-інтерфейсів C, які будуть обгортати наші існуючі API-інтерфейси C ++ для доступу до нашої основної логіки (написаної на об'єктно-орієнтованій C ++). По суті, це буде API для склеювання, який дозволяє використовувати нашу логіку C ++ іншими мовами. Які хороші підручники, книги чи найкращі практики вводять поняття, що стосуються обгортання C навколо об’єктно-орієнтованого C ++?


4
перегляньте джерело zeromq для натхнення. На даний момент бібліотека написана на C ++ і має прив'язки на мові C. zeromq.org
Хасан

1
Пов’язані (або навіть дублікат): Обтікання API класу C ++ для споживання C
користувач

Відповіді:


70

Це не надто складно зробити вручну, але це залежатиме від розміру вашого інтерфейсу. Випадки, коли я це робив, полягали в тому, щоб дозволити використання нашої бібліотеки 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 ++.


11
Я рекомендую вам використовувати щось інше, ніж void, наприклад анонімну структуру замість void * для поверненого об'єкта. Це може забезпечити певний тип безпеки для повернутих ручок. Перевірте stackoverflow.com/questions/839765 / ... для отримання додаткової інформації про це.
Laserallan

3
Я погоджуюсь з Laserallan і відповідно змінив мій код
Майкл Андерсон,

2
@Mike Weller Нове та видалення всередині зовнішнього блоку "C" чудово. Зовнішній вигляд "С" впливає лише на назву "манґлант". Компілятор C ніколи не бачить цей файл, лише заголовок.
Майкл Андерсон,

2
Я також пропустив typedef, необхідний для того, щоб все це скомпілювати в C. Дивна typdef struct Foo Foo; "рубати". Код оновлено
Майкл Андерсон,

5
@MichaelAnderson, у вашому myStruct_destroyі myStruct_doSomethingфункціях є дві помилки . Має бути reinterpret_cast<MyClass*>(v).
firegurafiku

17

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

Але, як згадував Майкл, зв’язування може стати досить складним.

Сподіваюся, це допомагає


2
Більш докладно про обробку винятків для цього випадку подивися на наступну тему: stackoverflow.com/questions/847279 / ...
Laserallan

2
Коли я знаю, що моя бібліотека C ++ також матиме API C, я інкапсулюю код помилки API int у своєму базовому класі винятків. На місці метання легше дізнатися, який саме стан помилки, і надати дуже конкретний код помилки. "Обгортки" try-catch у зовнішніх функціях API C просто потребують отримання коду помилки та повернення його абоненту. Інші стандартні винятки з бібліотеки див. У посиланні Laserallan.
Еміль Корм'є

2
catch (...) {} - це чисте чисте зло. Я шкодую лише про те, що я можу проголосувати лише один раз.
Террі Махаффі

2
@Terry Mahaffey Я абсолютно з вами згоден, що це зло. Найкраще робити те, що запропонував Еміль. Але якщо ви повинні гарантувати, що загорнутий код ніколи не викине, у вас немає іншого вибору, крім як поставити фіксатор (...) внизу всіх інших ідентифікованих уловів. Це так, оскільки бібліотека, яку ви обгортаєте, може бути погано документована. Немає конструкцій C ++, якими ви можете скористатися для того, щоб забезпечити, що може бути створений лише набір винятків. Що є меншим із двох зол? catch (...) або ризикує збій під час виконання, коли загорнутий код намагається передати абоненту C?
figurassa

1
catch (...) {std :: terminate (); } є прийнятним. catch (...) {} - потенційна діра в безпеці
Террі Махаффі

10

Неважко виставити код C ++ на C, просто використовуйте шаблон дизайну Facade

Я припускаю, що ваш код C ++ вбудований у бібліотеку, все, що вам потрібно зробити, - це зробити один модуль C у вашій бібліотеці C ++ як фасад для вашої бібліотеки разом із чистим заголовком C. Модуль C викличе відповідні функції C ++

Щойно ви це зробите, ваші програми та бібліотека C матимуть повний доступ до C api, який ви відкрили.

наприклад, ось зразок модуля Facade

#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


Привіт @hhafez, у вас є простий приклад у світі? Один із струнами?
Jason Foglia

для хлопця, який не займається
КПП,

6

Я думаю, ви зможете отримати деякі ідеї щодо напрямку та / або, можливо, використовувати безпосередньо 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. Також кілька інтерпретованих та скомпільованих реалізацій схеми Guile, MzScheme, Chicken). SWIG найчастіше використовується для створення інтерпретованих або скомпільованих середовищ програмування високого рівня, користувальницьких інтерфейсів і як інструмент для тестування та прототипування програмного забезпечення C / C ++. SWIG також може експортувати своє дерево синтаксичного аналізу у вигляді s-виразів XML та Lisp. SWIG можна вільно використовувати, розповсюджувати,


2
SWIG майже перебив, якщо все, що він хоче зробити, це зробити бібліотеку C ++ доступною від C.
hhafez

1
Це думка і не містить реальних корисних відгуків. SWIG допоможе, якщо оригінальний код: Швидко змінюється, для його підтримки немає ресурсів C ++, а доступні лише ресурси C, і якщо розробник хоче автоматизувати генерацію API C. Це загальні і безумовно вагомі причини використання SWIG.
користувач1363990

5

Просто замініть концепцію об’єкта на void *(яку часто називають непрозорим типом у бібліотеках, орієнтованих на C) і повторно використовуйте все, що ви знаєте з C ++.


2

Я думаю, що використання SWIG є найкращою відповіддю ... не тільки це дозволяє уникнути винаходу колеса, але воно є надійним, а також сприяє наступності в розробці, а не вирішенні проблеми.

Проблеми високої частоти потрібно вирішувати шляхом довгострокового вирішення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.