c++ c api class wrapping

Envolviendo API de clase C++ para el consumo de C



class wrapping (6)

Algunas opiniones de mi experiencia:

  • las funciones deben devolver códigos para representar errores. Es útil tener una función que devuelva la descripción del error en forma de cadena. Todos los demás valores devueltos deben estar fuera de los parámetros.

P.ej:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget);

  • ponga las firmas en las estructuras / clases a las que apunta el identificador para controlar las identidades de validez.

Por ejemplo, tu función debería verse así:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget){ Ui* ui = (Ui*)ui; if(ui.Signature != 1234) return BAD_HUI; }

  • Los objetos deben crearse y liberarse utilizando las funciones exportadas desde DLL, ya que el método de asignación de memoria en DLL y la aplicación consumidora puede diferir.

P.ej:

C_ERROR CreateUi(HUI* ui); C_ERROR CloseUi(HUI hui); // usually error codes don''t matter here, so may use void

  • si está asignando memoria para algún búfer u otros datos que pueden requerirse para permanecer fuera de su biblioteca, proporcione el tamaño de este búfer / datos. De esta forma, los usuarios pueden guardarlo en el disco, en la base de datos o donde quieran sin tener que hackear su interior para averiguar el tamaño real. De lo contrario, eventualmente tendrá que proporcionar su propia API de E / S de archivo que los usuarios usarán solo para convertir sus datos a una matriz de bytes de tamaño conocido.

P.ej:

C_ERROR CreateBitmap(HUI* ui, SIZE size, char** pBmpBuffer, int* pSize);

  • si sus objetos tienen alguna representación típica fuera de su biblioteca C ++, proporcione un medio de conversión a esta representación (por ejemplo, si tiene alguna clase Image y proporciona acceso a ella a través del identificador HIMG , proporcione funciones para convertirla ay desde, por ejemplo, ventanas HBITMAP) . Esto simplificará la integración con la API existente.

P.ej

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

Tengo un conjunto de clases de C ++ relacionadas que deben ser envueltas y exportadas desde una DLL de tal manera que las bibliotecas C / FFI puedan consumirlas fácilmente. Estoy buscando algunas "mejores prácticas" para hacer esto. Por ejemplo, cómo crear y liberar objetos, cómo manejar clases base, soluciones alternativas, etc.

Algunas pautas básicas que tengo hasta ahora es convertir métodos en funciones simples con un argumento adicional void * que representa el puntero ''this'', incluyendo cualquier destructores. Los constructores pueden conservar su lista de argumentos original, pero deben devolver un puntero que represente el objeto. Toda la memoria debe manejarse a través del mismo conjunto de asignaciones de todo el proceso y rutinas libres, y debe poder intercambiarse en caliente en cierto sentido, ya sea a través de macros o de otro modo.


El método público de Foreach necesita una función C.
También necesita un puntero opaco para representar su clase en el código C.
Es más simple usar un vacío * aunque podrías construir una estructura que contenga un vacío * y otra información (por ejemplo, si quisieras admitir arreglos).

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);

La implementación es trivial.
Convierta el puntero opaco a Fred y luego llame al método.

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); }


En primer lugar, es posible que no necesite convertir todos sus métodos en funciones C. Si puede simplificar la API y ocultar parte de la interfaz C ++, es mejor, ya que minimiza la posibilidad de cambiar la API C cuando cambia la lógica C ++.

Así que piense en una abstracción de nivel superior que se proporcionará a través de esa API. Use esa solución nula * que describió. Me parece el más apropiado (o typedef void * como HANDLE :)).



Si bien la respuesta de Loki Astari es muy buena, su código de muestra coloca el código de ajuste dentro de la clase C ++. Prefiero tener el código de envoltura en un archivo separado. También creo que es mejor estilo para prefijar las funciones C de envoltura con el nombre de la clase.

Las siguientes publicaciones de blog muestran cómo hacerlo: http://blog.eikke.com/index.php/ikke/2005/11/03/using_c_classes_in_c.html

Copié la parte esencial porque el blog está abandonado y podría desaparecer finalmente (crédito al blog de Ikke):

Primero necesitamos una clase C ++, usando un archivo de encabezado (Test.hh)

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

y un archivo de implementación (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; }

Esto es solo código básico de C ++.

Entonces necesitamos un código de pegamento. Este código es algo entre C y C ++. De nuevo, tenemos un archivo de encabezado (TestWrapper.h, solo .h ya que no contiene ningún código de 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

y las implementaciones de función (TestWrapper.cc, .cc, ya que contiene código 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; } }


Use vector (y string :: c_str) para intercambiar datos con API que no sean C ++. (Directriz n. ° 78 de C ++ Coding Standards , H. Sutter / A. Alexandrescu).

PD: No es tan cierto que "los constructores pueden conservar su lista de argumentos original". Esto solo es cierto para los tipos de argumentos que son compatibles con C.

PS2 Por supuesto, escucha Cătălin y mantén tu interfaz lo más pequeña y simple posible.