Entwicklung einer C-Wrapper-API für objektorientierten C++-Code

Lesezeit: 8 Minuten

Ich möchte eine Reihe von C-APIs entwickeln, die unsere vorhandenen C++-APIs umschließen, um auf unsere Kernlogik (in objektorientiertem C++ geschrieben) zuzugreifen. Dies wird im Wesentlichen eine Glue-API sein, die es unserer C++-Logik ermöglicht, von anderen Sprachen verwendet zu werden. Was sind einige gute Tutorials, Bücher oder Best Practices, die die Konzepte vorstellen, die beim Umschließen von C um objektorientiertes C++ erforderlich sind?

  • Sehen Sie sich die Zeromq-Quelle an, um sich inspirieren zu lassen. Die Bibliothek ist derzeit in C++ geschrieben und hat C-Bindungen. zeromq.org

    – Hassan Syed

    12. Januar 2010 um 19:26 Uhr


  • Verwandt (oder sogar ein Duplikat): Umhüllen der C++-Klassen-API für den C-Verbrauch

    – Benutzer

    17. Dezember 2014 um 9:32 Uhr

Entwicklung einer C Wrapper API fur objektorientierten C Code
Michael Anderson

Dies ist von Hand nicht allzu schwierig, hängt jedoch von der Größe Ihrer Schnittstelle ab. Die Fälle, in denen ich dies getan habe, waren, um die Verwendung unserer C++-Bibliothek aus reinem C-Code heraus zu ermöglichen, und daher war SWIG keine große Hilfe. (Nun, vielleicht kann SWIG dazu verwendet werden, aber ich bin kein SWIG-Guru und es schien nicht trivial zu sein.)

Alles, was wir am Ende taten, war:

  1. Jedes Objekt wird in C an einem undurchsichtigen Handle herumgereicht.
  2. Konstruktoren und Destruktoren werden in reine Funktionen verpackt
  3. Elementfunktionen sind reine Funktionen.
  4. Andere eingebaute Funktionen werden nach Möglichkeit C-Äquivalenten zugeordnet.

Also eine Klasse wie diese (C++ Header)

class MyClass
{
  public:
  explicit MyClass( std::string & s );
  ~MyClass();
  int doSomething( int j );
}

Würde auf eine C-Schnittstelle wie folgt abgebildet (C-Header):

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

Die Implementierung der Schnittstelle würde so aussehen (C++-Quelle)

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

Wir leiten unseren undurchsichtigen Griff von der ursprünglichen Klasse ab, um Gussteile zu vermeiden, und (Dies schien mit meinem aktuellen Compiler nicht zu funktionieren). Wir müssen das Handle zu einer Struktur machen, da C keine Klassen unterstützt.

Das gibt uns also die grundlegende C-Schnittstelle. Wenn Sie ein vollständigeres Beispiel wünschen, das eine Möglichkeit zeigt, wie Sie die Ausnahmebehandlung integrieren können, können Sie meinen Code auf github ausprobieren: https://gist.github.com/mikeando/5394166

Der unterhaltsame Teil besteht nun darin sicherzustellen, dass Sie alle erforderlichen C++-Bibliotheken korrekt in Ihre größere Bibliothek eingebunden bekommen. Für gcc (oder clang) bedeutet das, nur die letzte Linkphase mit g++ durchzuführen.

  • Ich würde Ihnen empfehlen, etwas anderes als void zu verwenden, zum Beispiel eine anonyme Struktur anstelle von void* für das zurückgegebene Objekt. Dies kann eine Art Sicherheit für die zurückgegebenen Griffe geben. Weitere Informationen dazu finden Sie unter stackoverflow.com/questions/839765/….

    – Laserallan

    12. Januar 2010 um 4:21 Uhr


  • Ich stimme Laserallan zu und habe meinen Code entsprechend umgestaltet

    – Michael Anderson

    12. Januar 2010 um 5:00 Uhr

  • @Mike Weller Das Neue und Löschen innerhalb des externen “C” -Blocks ist in Ordnung. Das externe “C” bewirkt nur die Namensverstümmelung. Der C-Compiler sieht diese Datei nie, nur den Header.

    – Michael Anderson

    17. Januar 2010 um 8:05 Uhr


  • Ich habe auch eine Typedef vermisst, die benötigt wird, um alles in C zu kompilieren. Die seltsame Typdef-Struktur Foo Foo; “hacken”. Code wird aktualisiert

    – Michael Anderson

    17. Januar 2010 um 8:06 Uhr

  • @MichaelAnderson, es gibt zwei Tippfehler in deinem myStruct_destroy und myStruct_doSomething Funktionen. Sollte sein reinterpret_cast<MyClass*>(v).

    – firegurafiku

    15. Juni 2014 um 5:47 Uhr

1646928609 847 Entwicklung einer C Wrapper API fur objektorientierten C Code
Figura

Ich denke, Michael Andersons Antwort ist auf dem richtigen Weg, aber mein Ansatz wäre anders. Sie müssen sich um eine zusätzliche Sache kümmern: Ausnahmen. Ausnahmen sind kein Teil von C ABI, daher dürfen Ausnahmen niemals hinter den C++-Code geworfen werden. Ihre Kopfzeile sieht also so aus:

#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

Und die .cpp-Datei Ihres Wrappers sieht so aus:

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

Noch besser: Wenn Sie wissen, dass alles, was Sie brauchen, eine einzige Instanz von MyStruct ist, gehen Sie nicht das Risiko ein, dass void-Zeiger an Ihre API übergeben werden. Mach stattdessen so etwas:

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

Diese API ist viel sicherer.

Aber, wie Michael erwähnte, das Verlinken kann ziemlich schwierig werden.

Hoffe das hilft

  • Weitere Informationen zur Ausnahmebehandlung für diesen Fall finden Sie im folgenden Thread: stackoverflow.com/questions/847279/…

    – Laserallan

    12. Januar 2010 um 4:13 Uhr

  • Wenn ich weiß, dass meine C++-Bibliothek auch eine C-API haben wird, kapsele ich einen API-Fehlercode int in meine Ausnahme-Basisklasse ein. Es ist einfacher, an der Wurfstelle zu wissen, was die genaue Fehlerbedingung ist, und einen sehr spezifischen Fehlercode anzugeben. Die Try-Catch-„Wrapper“ in den äußeren C-API-Funktionen müssen lediglich den Fehlercode abrufen und an den Aufrufer zurückgeben. Weitere Ausnahmen von Standardbibliotheken finden Sie unter Laserallans Link.

    – Emil Cormier

    13. Januar 2010 um 6:59 Uhr


  • catch(…){ } ist pures unverfälschtes Böses. Ich bedauere nur, dass ich nur einmal abstimmen kann.

    – Terry Mahaffey

    17. Januar 2010 um 8:13 Uhr

  • @ Terry Mahaffey Ich stimme dir absolut zu, dass es böse ist. Das Beste ist, das zu tun, was Emile vorgeschlagen hat. Aber wenn Sie garantieren müssen, dass der verpackte Code niemals geworfen wird, haben Sie keine andere Wahl, als einen Haken (…) an das Ende aller anderen identifizierten Haken zu setzen. Dies ist der Fall, weil die Bibliothek, die Sie umschließen, möglicherweise schlecht dokumentiert ist. Es gibt keine C++-Konstrukte, die Sie verwenden können, um zu erzwingen, dass nur eine Reihe von Ausnahmen ausgelöst werden darf. Was ist das kleinere von zwei Übeln? catch (…) oder einen Laufzeitabsturz riskieren, wenn der umschlossene Code versucht, an den C-Aufrufer zu werfen?

    – Figura

    17. Januar 2010 um 20:11 Uhr

  • catch(…) { std::terminate(); } ist akzeptabel. catch(…){ } ist eine potenzielle Sicherheitslücke

    – Terry Mahaffey

    17. Januar 2010 um 20:15 Uhr

Es ist nicht schwer, C++-Code für C verfügbar zu machen, verwenden Sie einfach das Facade-Entwurfsmuster

Ich gehe davon aus, dass Ihr C++-Code in eine Bibliothek integriert ist. Sie müssen lediglich ein C-Modul in Ihrer C++-Bibliothek als Fassade für Ihre Bibliothek zusammen mit einer reinen C-Header-Datei erstellen. Das C-Modul ruft die relevanten C++-Funktionen auf

Sobald Sie dies getan haben, haben Ihre C-Anwendungen und -Bibliotheken vollen Zugriff auf die von Ihnen bereitgestellte C-API.

Hier ist zum Beispiel ein Beispiel für ein Fassadenmodul

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

Sie stellen diese C-Funktion dann als Ihre API bereit und können sie frei als C-Bibliothek verwenden, ohne sich Gedanken darüber machen zu müssen

// file name "libIntrface.h"
extern int doObjectOrientedStuff(int *, int, char*);

Offensichtlich ist dies ein erfundenes Beispiel, aber dies ist der einfachste Weg, eine C++-Bibliothek für C verfügbar zu machen

  • Hi @hhafez, hast du ein einfaches Hallo-Welt-Beispiel? Einer mit Saiten?

    – Jason Foglia

    4. Juli 2016 um 5:06 Uhr

  • Für einen Nicht-CPP-Typen ist das reizend

    – Nicklas Avén

    17. November 2017 um 12:55 Uhr

Ich würde denken, dass Sie in der Lage sein könnten, einige Ideen zur Richtung zu bekommen und / oder möglicherweise direkt zu verwenden SCHLUCK. Ich denke, dass das Durchgehen einiger Beispiele Ihnen zumindest eine Vorstellung davon geben würde, welche Dinge zu beachten sind, wenn Sie eine API in eine andere einbinden. Die Übung könnte von Vorteil sein.

SWIG ist ein Softwareentwicklungstool, das in C und C++ geschriebene Programme mit einer Vielzahl von höheren Programmiersprachen verbindet. SWIG wird mit verschiedenen Arten von Sprachen verwendet, einschließlich gängiger Skriptsprachen wie Perl, PHP, Python, Tcl und Ruby. Die Liste der unterstützten Sprachen umfasst auch Nicht-Skriptsprachen wie C#, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), Java, Lua, Modula-3, OCAML, Octave und R. Außerdem mehrere interpretierte und kompilierte Scheme-Implementierungen ( Guile, MzScheme, Chicken) werden unterstützt. SWIG wird am häufigsten verwendet, um hochrangige interpretierte oder kompilierte Programmierumgebungen, Benutzeroberflächen und als Werkzeug zum Testen und Prototyping von C/C++-Software zu erstellen. SWIG kann seinen Analysebaum auch in Form von XML- und Lisp-S-Ausdrücken exportieren. SWIG darf für kommerzielle und nichtkommerzielle Zwecke frei verwendet, verteilt und modifiziert werden.

Ersetzen Sie einfach das Konzept eines Objekts durch a void * (oft als undurchsichtiger Typ in C-orientierten Bibliotheken bezeichnet) und alles wiederverwenden, was Sie aus C++ kennen.

1646928609 838 Entwicklung einer C Wrapper API fur objektorientierten C Code
danbo

Ich denke, die Verwendung von SWIG ist die beste Antwort … es vermeidet nicht nur, das Rad neu zu erfinden, sondern es ist zuverlässig und fördert auch eine Kontinuität in der Entwicklung, anstatt das Problem zu lösen.

Hochfrequenzprobleme müssen durch eine langfristige Lösung angegangen werden.

988400cookie-checkEntwicklung einer C-Wrapper-API für objektorientierten C++-Code

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy