2009-10-19 2 views
23

У меня есть набор связанных классов C++, которые необходимо обернуть и экспортировать из DLL таким образом, чтобы их можно было легко использовать библиотеками C/FFI. Я ищу некоторые «лучшие практики» для этого. Например, как создавать и освобождать объекты, как обрабатывать базовые классы, альтернативные решения и т. Д.Обтекание API класса C++ для потребления C

Некоторые основные рекомендации, которые я имею до сих пор, - это преобразовать методы в простые функции с дополнительным аргументом void * этот 'указатель, включая любые деструкторы. Конструкторы могут сохранять свой исходный список аргументов, но должны возвращать указатель, представляющий объект. Вся память должна обрабатываться с помощью одного и того же набора распределений по всему процессу и бесплатных подпрограмм и должна быть с возможностью «горячей» замены в некотором смысле либо с помощью макросов, либо иным образом.

+0

Связанные (или даже дубликат): [Разработка C обертку API для объектно-ориентированного кода C++] (https://stackoverflow.com/questions/2045774/developing-c -wrapper-api-for-object-oriented-c-code) – user

ответ

25

Предложенный общественный метод требует функции C.
Вам также нужен непрозрачный указатель для представления вашего класса в коде C.
Проще просто использовать void *, хотя вы могли бы построить структуру, содержащую void * и другую информацию (например, если вы хотите поддерживать массивы?).

Fred.h 
-------------------------------- 

#ifdef __cplusplus 
class Fred 
{ 
    public: 
    Fred(int x,int y); 
    int doStuff(int p); 
}; 
#endif 

// 
// C Interface. 
typedef void* CFred; 

// 
// Need an explicit constructor and destructor. 
extern "C" CFred newCFred(int x,int y); 
extern "C" void delCFred(CFred); 

// 
// Each public method. Takes an opaque reference to the object 
// that was returned from the above constructor plus the methods parameters. 
extern "C" int doStuffCFred(CFred,int p); 

Реализация тривиальная.
Преобразовать непрозрачный указатель на Фред, а затем вызвать метод.

CFred.cpp 
-------------------------------- 

// Functions implemented in a cpp file. 
// But note that they were declared above as extern "C" this gives them 
// C linkage and thus are available from a C lib. 
CFred newCFred(int x,int y) 
{ 
    return reinterpret_cast<void*>(new Fred(x,y)); 
} 

void delCFred(CFred fred) 
{ 
    delete reinterpret_cast<Fred*>(fred); 
} 

int doStuffCFred(CFred fred,int p) 
{ 
    return reinterpret_cast<Fred*>(fred)->doStuff(p); 
} 
+5

'typedef void * CFred;' слишком общий, который вызывает проблему. Я бы использовал 'typedef Foo * CFred;', который, я думаю, лучше, потому что он заставляет компилятор выполнять некоторую проверку типа. Теперь вы не можете передать какой-либо тип функции C, которая принимает аргумент «CFred». – Nawaz

+0

Я этому не верю. Как можно это доказать? Это всего лишь указатель? – Nawaz

+2

Я бы использовал ключевое слово 'struct' вместо' class', если бы я это сделал, чтобы оба компилятора поняли код. То есть, я сделаю это: '#ifdef __cplusplus struct Fred {...}; #else struct Fred; typedef Fred * CFred; # endif' – Nawaz

4

Во-первых, вам не нужно преобразовывать все ваши методы в функции C. Если вы можете упростить API и скрыть некоторые из интерфейса C++, это лучше, поскольку вы минимизируете возможность изменения API-интерфейса C, когда вы меняете логику C++.

Итак, подумайте над абстракцией более высокого уровня, которая будет предоставляться через этот API. Используйте это решение void *, которое вы описали. Мне кажется наиболее подходящим (или typedef void * как HANDLE :)).

+0

Для этого проекта мне очень нужно сопоставление «один к одному» со всеми методами класса. Я обертываю класс «Ассемблер», который, среди прочего, имеет около 50 или около того методов, представляющих каждую инструкцию в наборе инструкций. Существуют также другие классы, представляющие регистры, ячейки памяти, указатели и т. Д. – Exponent

2

Используйте вектор (и строку :: c_str) для обмена данными с API не C++. (Руководство № 78 от C++ Coding Standards, H. Sutter/A. Alexandrescu).

PS Это не так, что «конструкторы могут сохранить свой первоначальный список аргументов». Это справедливо только для типов аргументов, которые являются C-совместимыми.

PS2 Конечно, послушайте Cătălin и держите свой интерфейс как можно малым и простым.

3

Некоторые мнения из моего опыта:

  • функции должны возвращать коды для представления ошибок. Полезно иметь функцию, возвращающую описание ошибки в строковой форме. Все остальные возвращаемые значения должны быть вне параметров.

т.д .:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget); 
  • подписались в структуры/классы ваши ручки указатель на проверки ручки на validness.

E.g. ваша функция должна выглядеть следующим образом:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget){ 
    Ui* ui = (Ui*)ui; 
    if(ui.Signature != 1234) 
    return BAD_HUI; 
} 
  • объекты должны быть созданы и выпущены с использованием функции, экспортируемой из DLL, так как метод распределения памяти в DLL и потребляя приложение может отличаться.

E.g.:

C_ERROR CreateUi(HUI* ui); 
C_ERROR CloseUi(HUI hui); // usually error codes don't matter here, so may use void 
  • если вы выделения памяти для некоторого буфера или других данных, которые могут потребоваться для сохраняться за пределами вашей библиотеки, обеспечить размер этого буфера/данных. Таким образом, пользователи могут сохранять их на диск, базу данных или там, где они хотят, не взламывая внутренности, чтобы узнать фактический размер. В противном случае вам, в конце концов, нужно будет предоставить свой собственный файл I/O api, который пользователи будут использовать только для преобразования ваших данных в массив байтов известного размера.

Например:

C_ERROR CreateBitmap(HUI* ui, SIZE size, char** pBmpBuffer, int* pSize); 
  • если ваши объекты имеют некоторые типичные представления за пределами ваших C++ библиотеки, обеспечивает среднее преобразования в это представление (например, если у вас есть некоторый класс Image и обеспечивают доступ к он через HIMG дескриптор, предоставляет функции для его преобразования и, например, из окна HBITMAP). Это упростит интеграцию с существующим API.

E.g.

C_ERROR BitmapToHBITMAP(HUI* ui, char* bmpBuffer, int size, HBITMAP* phBmp); 
12

Хотя ответ Локи Астари очень хорош, его пример кода помещает код обертывания в класс C++. Я предпочитаю иметь код упаковки в отдельный файл. Также я считаю, что лучше стилизовать префикс для функций C с именем класса.

Следующие записи в блоге показано, как сделать это: http://blog.eikke.com/index.php/ikke/2005/11/03/using_c_classes_in_c.html

Я скопировал существенную роль, так как блог заброшен и может, наконец, исчезают (кредит блог IKKE в):


Сначала мы нужен класс C++, используя один файл заголовка (Test.hh)

class Test { 
    public: 
     void testfunc(); 
     Test(int i); 

    private: 
     int testint; 
}; 

и один файл реализации (Test.cc)

#include <iostream> 
#include "Test.hh" 

using namespace std; 

Test::Test(int i) { 
    this->testint = i; 
} 

void Test::testfunc() { 
    cout << "test " << this->testint << endl; 
} 

Это всего лишь базовый код на C++.

Затем нам нужен код клея. Этот код представляет собой нечто среднее между C и C++. Опять же, мы получили один заголовочный файл (TestWrapper.h, просто .h, поскольку он не содержит какой-либо C++ кода)

typedef void CTest; 

#ifdef __cplusplus 
extern "C" { 
#endif 

CTest * test_new(int i); 
void test_testfunc(const CTest *t); 
void test_delete(CTest *t); 
#ifdef __cplusplus 
} 
#endif 

и реализацию функций (TestWrapper.cc,.куб.см, поскольку она содержит C++ код):

#include "TestWrapper.h" 
#include "Test.hh" 

extern "C" { 

    CTest * test_new(int i) { 
     Test *t = new Test(i); 

     return (CTest *)t; 
    } 

    void test_testfunc(const CTest *test) { 
     Test *t = (Test *)test; 
     t->testfunc(); 
    } 

    void test_delete(CTest *test) { 
     Test *t = (Test *)test; 

     delete t; 
    } 
} 
+0

очень ясно! thx – malat

+0

Я считаю, что более чисто определить тип C-типа как void * вместо void, как это сделал Loki, поскольку объект класса будет передаваться между кодом C & C++ как void *. –

Смежные вопросы