Empaquetar la API de clase C++ para el consumo de C


Tengo un conjunto de clases C++ relacionadas que deben ser empaquetadas y exportadas desde una DLL de tal manera que puedan ser consumidas fácilmente por las bibliotecas C / FFI. 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 void* adicional que representa el puntero 'this', incluyendo cualquier destructor. Los constructores pueden conservar su lista de argumentos original, pero deben devolver un puntero que represente el objeto. Toda la memoria debe ser manejada a través del mismo conjunto de asignación de todo el proceso y rutinas libres, y debe ser intercambiable en caliente en un sentido, ya sea a través de macros o de otra manera.

Author: Vertexwahn, 2009-10-19

6 answers

Foreach método público necesita una función C.
También necesita un puntero opaco para representar su clase en el código C.
Es más sencillo simplemente usar un void*, aunque podría construir una estructura que contenga un void * y otra información (por ejemplo, si desea admitir matrices?).

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 un 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);
}
 27
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-19 19:20:35

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

Las siguientes entradas del blog muestran cómo hacer eso: 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 finalmente podría desaparecer (crédito a El 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 algún código de pegamento. Este código es algo entre C y C++. Una vez más, tenemos un archivo de encabezado (TestWrapper.h, solo .h ya que no contiene ningún código 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 funciones (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;
    }
}
 15
Author: codingFriend1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-15 15:28:44

Primero, es posible que no necesite convertir todos sus métodos a funciones C. Si puede simplificar la API y ocultar parte de la interfaz de C++, es mejor, ya que minimiza la posibilidad de cambiar la API de C cuando cambia la lógica de C++ detrás.

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

 4
Author: Cătălin Pitiș,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-19 13:53:56

Algunas opiniones de mi experiencia:

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

Ej:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget);
  • coloque las firmas en las estructuras/clases a las que su puntero se dirige para comprobar la validez de los controladores.

Por ejemplo, su función debería tener el siguiente aspecto:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget){
    Ui* ui = (Ui*)ui;
    if(ui.Signature != 1234)
    return BAD_HUI;
}
  • los objetos deben ser creados y liberado usando funciones exportadas desde DLL, ya que el método de asignación de memoria en DLL y la aplicación consumidora pueden diferir.

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 puedan ser necesarios para persistir fuera de su biblioteca, proporcione el tamaño de este búfer/datos. De esta manera, los usuarios pueden guardarlo en el disco, la base de datos o donde quieran sin piratear sus componentes internos para averiguar el tamaño real. De lo contrario, eventualmente tendrá que proporcionar su propia api de E/S de archivos qué usuarios usarán solo para convertir sus datos a una matriz de bytes de tamaño conocido.

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 de 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 de HIMG handle, proporcione funciones para convertirla hacia y desde, por ejemplo, windows HBITMAP). Esto simplificará la integración con la API existente.

Por ejemplo

C_ERROR BitmapToHBITMAP(HUI* ui, char* bmpBuffer, int size, HBITMAP* phBmp);
 3
Author: elder_george,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-19 14:17:08

Utilice vector (y string::c_str) para intercambiar datos con API que no sean de C++. (Directriz # 78 de Estándares de Codificación C++ , H. Sutter / A. Alexandrescu).

PS 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.

 2
Author: Daniel Daranas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:33
 2
Author: Doug T.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-20 09:32:58