Обтекание API класса С++ для потребления C
У меня есть набор связанных классов С++, которые необходимо обернуть и экспортировать из DLL таким образом, чтобы их можно было легко использовать библиотеками C/FFI. Я ищу некоторые "лучшие практики" для этого. Например, как создавать и освобождать объекты, как обращаться с базовыми классами, альтернативными решениями и т.д.
Некоторые основные рекомендации, которые я имею до сих пор, - это преобразовать методы в простые функции с дополнительным аргументом void *, представляющим указатель 'this', включая любые деструкторы. Конструкторы могут сохранять свой исходный список аргументов, но должны возвращать указатель, представляющий объект. Вся память должна обрабатываться с помощью одного и того же набора распределений по всему процессу и бесплатных подпрограмм и должна быть с возможностью "горячей" замены в некотором смысле либо с помощью макросов, либо иначе.
Ответы
Ответ 1
В обычном общедоступном методе вам нужна функция 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);
Реализация тривиальна.
Преобразуйте непрозрачный указатель на Fred, а затем вызовите метод.
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);
}
Ответ 2
В то время как ответ Loki Astari очень хорош, его примерный код помещает код упаковки внутри класса С++. Я предпочитаю иметь код упаковки в отдельный файл. Кроме того, я считаю, что лучше стилизовать префикс обертывающих C-функций с именем класса.
Следующие сообщения в блогах показывают, как это сделать:
http://blog.eikke.com/index.php/ikke/2005/11/03/using_c_classes_in_c.html
Я скопировал основную часть, потому что блог оставлен и может окончательно исчезнуть (кредит для Ikke Blog):
Сначала нам нужен класс С++, используя один заголовочный файл (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 и С++. Опять же, у нас есть один заголовочный файл (TestWrapper.h, просто .h, так как он не содержит кода на С++)
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,.cc, поскольку он содержит код С++):
#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;
}
}
Ответ 3
Сначала вам может не понадобиться преобразовывать все ваши методы в функции C. Если вы можете упростить API и скрыть некоторые из интерфейса С++, это лучше, поскольку вы минимизируете возможность изменения API-интерфейса C, когда вы меняете логику С++.
Итак, подумайте над абстракцией более высокого уровня, которая будет предоставляться через этот API. Используйте это решение void *, которое вы описали. Мне кажется наиболее подходящим (или typedef void * как HANDLE:)).
Ответ 4
Некоторые мнения из моего опыта:
- функции должны возвращать коды для представления ошибок. Полезно иметь функцию, возвращающую описание ошибки в строковой форме. Все остальные возвращаемые значения должны быть вне параметров.
например:.
C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget);
- помещает подписи в структуры/классы, указатели на указатели на проверку дескрипторов на правильность.
например. ваша функция должна выглядеть так:
C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget){
Ui* ui = (Ui*)ui;
if(ui.Signature != 1234)
return BAD_HUI;
}
-
Объекты
- должны быть созданы и выпущены с использованием функций, экспортированных из DLL, так как метод распределения памяти в DLL и приложении-потребителе может отличаться.
например:.
C_ERROR CreateUi(HUI* ui);
C_ERROR CloseUi(HUI hui); // usually error codes don't matter here, so may use void
- Если вы выделяете память для некоторого буфера или других данных, которые могут потребоваться для сохранения за пределами вашей библиотеки, укажите размер этого буфера/данных. Таким образом, пользователи могут сохранять их на диск, базу данных или там, где они хотят, не взламывая внутренности, чтобы узнать фактический размер. В противном случае вам в конечном итоге необходимо будет предоставить собственный файл ввода/вывода api, который пользователи будут использовать только для преобразования ваших данных в массив байтов известного размера.
например:.
C_ERROR CreateBitmap(HUI* ui, SIZE size, char** pBmpBuffer, int* pSize);
- Если у ваших объектов есть типичное представление вне вашей библиотеки С++, укажите среднее значение для преобразования в это представление (например, если у вас есть некоторый класс
Image
и предоставить ему доступ через дескриптор HIMG
, предоставьте функции для его преобразования к и из, например, окна HBITMAP). Это упростит интеграцию с существующим API.
например.
C_ERROR BitmapToHBITMAP(HUI* ui, char* bmpBuffer, int size, HBITMAP* phBmp);
Ответ 5
Используйте вектор (и string:: c_str) для обмена данными с API-интерфейсами, отличными от С++. (Руководство № 78 от Стандарты кодирования С++, H. Sutter/A. Alexandrescu).
PS Это не так, что "конструкторы могут сохранить свой первоначальный список аргументов". Это справедливо только для типов аргументов, совместимых с C.
PS2 Конечно, слушайте Cătălin и держите свой интерфейс как можно малым и простым.
Ответ 6
Это может представлять интерес: "Смешивание C и С++" на С++ FAQ Lite. В частности, [32.8] Как передать объект класса С++ в/из функции C?