Je cherche à développer un ensemble d'API C qui encapsuleront nos API C++ existantes pour accéder à notre logique de base (écrite en C++ orienté objet). Ce sera essentiellement une API glue qui permettra à notre logique C++ d'être utilisable par d'autres langages. Quels sont les bons didacticiels, livres ou meilleures pratiques qui présentent les concepts impliqués dans l'encapsulation du C autour du C++ orienté objet?
Ce n'est pas trop difficile à faire à la main, mais cela dépendra de la taille de votre interface. Les cas où je l'ai fait étaient de permettre l'utilisation de notre bibliothèque C++ à partir de code C pur, et donc SWIG n'était pas d'une grande aide. (Eh bien peut-être que SWIG peut être utilisé pour faire cela, mais je ne suis pas un gourou SWIG et cela semblait non trivial)
Tout ce que nous avons fini par faire, c'est:
Donc une classe comme ça (en-tête C++)
class MyClass
{
public:
explicit MyClass( std::string & s );
~MyClass();
int doSomething( int j );
}
Serait mappé à une interface C comme celle-ci (en-tête 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 );
L'implémentation de l'interface ressemblerait à ceci (source 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);
}
}
Nous tirons notre poignée opaque de la classe d'origine pour éviter d'avoir besoin de coulée, et (Cela ne semble pas fonctionner avec mon complier actuel). Nous devons faire du handle une structure car C ne prend pas en charge les classes.
Cela nous donne donc l'interface C de base. Si vous voulez un exemple plus complet montrant une façon d'intégrer la gestion des exceptions, alors vous pouvez essayer mon code sur github: https://Gist.github.com/mikeando/5394166
La partie amusante garantit maintenant que vous obtenez correctement toutes les bibliothèques C++ requises dans votre plus grande bibliothèque. Pour gcc (ou clang), cela signifie simplement faire la dernière étape du lien en utilisant g ++.
Je pense que la réponse de Michael Anderson est sur la bonne voie, mais mon approche serait différente. Vous devez vous soucier d'une chose supplémentaire: les exceptions. Les exceptions ne font pas partie de C ABI, vous ne pouvez donc pas laisser d'exceptions être levées au-delà du code C++. Donc, votre en-tête va ressembler à ceci:
#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
Et le fichier .cpp de votre wrapper ressemblera à ceci:
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;
}
Encore mieux: si vous savez que tout ce dont vous avez besoin en tant qu'instance unique de MyStruct, ne prenez pas le risque de traiter les pointeurs vides transmis à votre API. Faites à la place quelque chose comme ça:
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;
}
Cette API est beaucoup plus sûre.
Mais, comme Michael l'a mentionné, la liaison peut devenir assez délicate.
J'espère que cela t'aides
Il n'est pas difficile d'exposer du code C++ en C, utilisez simplement le modèle de conception de façade
Je suppose que votre code C++ est intégré dans une bibliothèque, tout ce que vous devez faire est de créer un module C dans votre bibliothèque C++ en tant que façade de votre bibliothèque avec un fichier d'en-tête C pur. Le module C appellera les fonctions C++ pertinentes
Une fois que vous avez fait cela, vos applications C et votre bibliothèque auront un accès complet à l'API C que vous avez exposée.
par exemple, voici un exemple de module Facade
#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);
}
vous exposez ensuite cette fonction C en tant qu'API et vous pouvez l'utiliser librement en tant que C lib sans vous soucier de
// file name "libIntrface.h"
extern int doObjectOrientedStuff(int *, int, char*);
Évidemment, c'est un exemple artificiel, mais c'est le moyen le plus simple d'exposer une bibliothèque C++ à C
Je pense que vous pourriez avoir des idées sur la direction et/ou éventuellement utiliser directement SWIG . Je pense que le fait de passer en revue quelques-uns des exemples vous donnerait au moins une idée du type de choses à considérer lors de l'encapsulation d'une API dans une autre. L'exercice pourrait être bénéfique.
SWIG est un outil de développement logiciel qui connecte des programmes écrits en C et C++ avec une variété de langages de programmation de haut niveau. SWIG est utilisé avec différents types de langages, y compris les langages de script courants tels que Perl, PHP, Python, Tcl et Ruby. La liste des langages pris en charge comprend également des langages sans script tels que C #, Common LISP (CLISP, Allegro CL, CFFI, UFFI), Java, Lua, Modula-3, OCAML, Octave et R. Également plusieurs implémentations Scheme interprétées et compilées ( Guile, MzScheme, Chicken) sont pris en charge. SWIG est le plus souvent utilisé pour créer des environnements de programmation interprétés ou compilés de haut niveau, des interfaces utilisateur et comme outil de test et de prototypage de logiciels C/C++. SWIG peut également exporter son arbre d'analyse sous la forme d'expressions s XML et LISP. SWIG peut être librement utilisé, distribué et modifié à des fins commerciales et non commerciales.
Remplacez simplement le concept d'un objet par un void *
(souvent appelé type opaque dans les bibliothèques orientées C) et réutilisez tout ce que vous savez de C++.
Je pense que l'utilisation de SWIG est la meilleure réponse ... non seulement il évite de réinventer la roue mais il est fiable et favorise également une continuité de développement plutôt que de tirer sur le problème.
Les problèmes à haute fréquence doivent être résolus par une solution à long terme.