Desarrollo de API contenedora de C para código C++ orientado a objetos
Estoy buscando desarrollar un conjunto de API de C que se ajusten a nuestras API de C++ existentes para acceder a nuestra lógica central (escrita en C++ orientado a objetos). Básicamente, será una API adhesiva que permitirá que otros lenguajes puedan utilizar nuestra lógica C++. ¿Cuáles son algunos buenos tutoriales, libros o mejores prácticas que introducen los conceptos involucrados en la integración de C en C++ orientado a objetos?
Esto no es demasiado difícil de hacer a mano, pero dependerá del tamaño de su interfaz. Los casos en los que lo hice fueron para permitir el uso de nuestra biblioteca C++ desde código C puro y, por lo tanto, SWIG no fue de mucha ayuda. (Bueno, tal vez SWIG pueda usarse para hacer esto, pero no soy un gurú de SWIG y no parecía trivial)
Todo lo que terminamos haciendo fue:
- Cada objeto se pasa en C por un asa opaca.
- Los constructores y destructores están envueltos en funciones puras.
- Las funciones miembro son funciones puras.
- Otras funciones integradas se asignan a equivalentes de C siempre que sea posible.
Entonces, una clase como esta (encabezado C++)
class MyClass
{
public:
explicit MyClass( std::string & s );
~MyClass();
int doSomething( int j );
}
Se asignaría a una interfaz C como esta (encabezado 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 );
La implementación de la interfaz se vería así (fuente 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);
}
}
Derivamos nuestro identificador opaco de la clase original para evitar la necesidad de realizar conversiones y (esto no pareció funcionar con mi compilador actual). Tenemos que convertir el identificador en una estructura ya que C no admite clases.
Eso nos da la interfaz C básica. Si desea un ejemplo más completo que muestre una forma de integrar el manejo de excepciones, puede probar mi código en github: https://gist.github.com/mikeando/5394166
La parte divertida ahora es asegurarse de que todas las bibliotecas de C++ necesarias se vinculen correctamente a su biblioteca más grande. Para gcc (o clang) eso significa simplemente hacer la etapa de enlace final usando g++.
Creo que la respuesta de Michael Anderson va por buen camino, pero mi enfoque sería diferente. Tienes que preocuparte por una cosa extra: las excepciones. Las excepciones no son parte de la ABI de C, por lo que no puede permitir que se lancen excepciones más allá del código C++. Entonces tu encabezado se verá así:
#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
Y el archivo .cpp de su contenedor se verá así:
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;
}
Aún mejor: si sabe que todo lo que necesita es una instancia única de MyStruct, no se arriesgue a lidiar con punteros vacíos que se pasan a su API. Haz algo como esto en su lugar:
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;
}
Esta API es mucho más segura.
Pero, como mencionó Michael, vincular puede resultar bastante complicado.
Espero que esto ayude
No es difícil exponer el código C++ a C, simplemente use el patrón de diseño Facade
Supongo que su código C++ está integrado en una biblioteca, todo lo que necesita hacer es crear un módulo C en su biblioteca C++ como fachada de su biblioteca junto con un archivo de encabezado C puro. El módulo C llamará a las funciones C++ relevantes.
Una vez que lo haga, sus aplicaciones y biblioteca de C tendrán acceso completo a la API de C que expuso.
por ejemplo, aquí hay un módulo de fachada de muestra
#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);
}
luego expones esta función C como tu API y puedes usarla libremente como una biblioteca C sin preocuparte por
// file name "libIntrface.h"
extern int doObjectOrientedStuff(int *, int, char*);
Obviamente, este es un ejemplo artificial, pero es la forma más sencilla de exponer una biblioteca C++ a C.
Creo que es posible que pueda obtener algunas ideas sobre la dirección y/o posiblemente utilizar SWIG directamente . Creo que repasar algunos de los ejemplos al menos le daría una idea de qué tipo de cosas considerar al integrar una API en otra. El ejercicio podría resultar beneficioso.
SWIG es una herramienta de desarrollo de software que conecta programas escritos en C y C++ con una variedad de lenguajes de programación de alto nivel. SWIG se utiliza con diferentes tipos de lenguajes, incluidos lenguajes de secuencias de comandos comunes como Perl, PHP, Python, Tcl y Ruby. La lista de lenguajes admitidos también incluye lenguajes que no son de secuencias de comandos, como C#, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), Java, Lua, Modula-3, OCAML, Octave y R. También varias implementaciones de Scheme interpretadas y compiladas ( Guile, MzScheme, Chicken) son compatibles. SWIG se utiliza más comúnmente para crear entornos de programación compilados o interpretados de alto nivel, interfaces de usuario y como herramienta para probar y crear prototipos de software C/C++. SWIG también puede exportar su árbol de análisis en forma de expresiones S XML y Lisp. SWIG puede usarse, distribuirse y modificarse libremente para uso comercial y no comercial.
Simplemente reemplace el concepto de objeto con void *
(a menudo denominado tipo opaco en las bibliotecas orientadas a C) y reutilice todo lo que sabe de C++.