web-dev-qa-db-fra.com

Développement de l'API C wrapper pour le code C ++ orienté objet

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?

73
theactiveactor

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:

  1. Chaque objet est passé en C une poignée opaque.
  2. Les constructeurs et les destructeurs sont enveloppés de fonctions pures
  3. Les fonctions membres sont de pures fonctions.
  4. Dans la mesure du possible, les autres éléments intégrés sont mappés sur des équivalents C.

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

63
Michael Anderson

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

14
figurassa

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

9
hhafez

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.

6
harschware

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

4
Hassan Syed

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.

2
danbo