Opracowywanie API opakowującego C dla kodu C ++ zorientowanego obiektowo

81

Chcę opracować zestaw interfejsów API C, które będą otaczać nasze istniejące interfejsy API C ++, aby uzyskać dostęp do naszej podstawowej logiki (napisanej w zorientowanym obiektowo C ++). Zasadniczo będzie to interfejs API kleju, który umożliwia korzystanie z naszej logiki C ++ w innych językach. Jakie są dobre samouczki, książki lub sprawdzone metody, które wprowadzają pojęcia związane z opakowaniem języka C wokół zorientowanego obiektowo języka C ++?

czynny
źródło
4
poszukaj inspiracji w źródle zeromq. Biblioteka jest obecnie napisana w C ++ i ma powiązania C. zeromq.org
Hassan Syed
1
Powiązane (lub nawet duplikat): Pakowanie interfejsu API klasy C ++ do użytku w języku C
użytkownik

Odpowiedzi:

70

Nie jest to zbyt trudne do zrobienia ręcznie, ale zależy to od rozmiaru interfejsu. Przypadki, w których to zrobiłem, polegały na umożliwieniu korzystania z naszej biblioteki C ++ z poziomu czystego kodu C, a zatem SWIG nie był zbyt pomocny. (Cóż, może SWIG może być do tego użyty, ale nie jestem guru SWIG i wydawało się to nietrywialne)

Skończyło się na tym, że:

  1. Każdy obiekt jest przekazywany w języku C przez nieprzezroczysty uchwyt.
  2. Konstruktory i destruktory są opakowane w czyste funkcje
  3. Funkcje składowe są funkcjami czystymi.
  4. Inne polecenia wbudowane są mapowane na odpowiedniki języka C.

Czyli taka klasa (nagłówek C ++)

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

Zmapowałoby na interfejs C w ten sposób (nagłówek 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 );

Implementacja interfejsu wyglądałaby następująco (źródło 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);
  }
}

Wyprowadzamy nasz nieprzezroczysty uchwyt z oryginalnej klasy, aby uniknąć konieczności odlewania i (To nie wydawało się działać z moim obecnym komplementem). Musimy uczynić uchwyt strukturą, ponieważ C nie obsługuje klas.

To daje nam podstawowy interfejs C. Jeśli potrzebujesz bardziej kompletnego przykładu pokazującego jeden sposób, w jaki możesz zintegrować obsługę wyjątków, możesz wypróbować mój kod na github: https://gist.github.com/mikeando/5394166

Zabawną częścią jest teraz upewnienie się, że wszystkie wymagane biblioteki C ++ są poprawnie połączone z większą biblioteką. W przypadku gcc (lub clang) oznacza to po prostu wykonanie ostatniego etapu linkowania za pomocą g ++.

Michael Anderson
źródło
11
Zalecałbym użycie czegoś innego niż void, na przykład anonimowej struktury zamiast void * dla zwróconego obiektu. Może to dać pewien rodzaj bezpieczeństwa dla zwróconych uchwytów. Sprawdź stackoverflow.com/questions/839765/…, aby uzyskać więcej informacji na ten temat.
Laserallan
3
Zgadzam się z Laserallanem i odpowiednio przeredagowałem mój kod
Michael Anderson
2
@Mike Weller Nowe i usuwanie wewnątrz zewnętrznego bloku „C” jest w porządku. Zewnętrzne „C” wpływa tylko na zniekształcenie nazwy. Kompilator C nigdy nie widzi tego pliku, tylko nagłówek.
Michael Anderson
2
Brakowało mi również wpisu typedef potrzebnego do skompilowania go w C. Dziwna struktura typdef Foo Foo; "włamać się". Kod jest aktualizowany
Michael Anderson
5
@MichaelAnderson, w Twoim myStruct_destroyi myStruct_doSomethingfunkcjach są dwie literówki . Powinien być reinterpret_cast<MyClass*>(v).
firegurafiku
17

Myślę, że odpowiedź Michaela Andersona jest na dobrej drodze, ale moje podejście byłoby inne. Musisz się martwić o jedną dodatkową rzecz: wyjątki. Wyjątki nie są częścią C ABI, więc nie można pozwolić, aby wyjątki były kiedykolwiek wyrzucane poza kod C ++. Więc twój nagłówek będzie wyglądał tak:

#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

A plik .cpp twojego opakowania będzie wyglądał następująco:

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

Nawet lepiej: jeśli wiesz, że wszystko, czego potrzebujesz jako pojedyncza instancja MyStruct, nie ryzykujesz, że wskaźniki void zostaną przekazane do twojego API. Zamiast tego zrób coś takiego:

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

Ten interfejs API jest o wiele bezpieczniejszy.

Ale, jak wspomniał Michael, tworzenie linków może być dość trudne.

Mam nadzieję że to pomoże

figurassa
źródło
2
Aby uzyskać więcej informacji na temat obsługi wyjątków w tym przypadku,
zapoznaj się
2
Kiedy wiem, że moja biblioteka C ++ będzie miała również C API, hermetyzuję kod błędu API int wewnątrz mojej klasy bazowej wyjątku. W miejscu rzucania łatwiej jest dowiedzieć się, jaki jest dokładny stan błędu i podać bardzo szczegółowy kod błędu. „Opakowania” try-catch w zewnętrznych funkcjach API C muszą po prostu pobrać kod błędu i zwrócić go wywołującemu. Aby zapoznać się z innymi wyjątkami dotyczącymi bibliotek standardowych, przejdź do łącza Laserallan.
Emile Cormier
2
catch (...) {} jest czystym nieskażonym złem. Żałuję tylko, że mogę głosować tylko raz.
Terry Mahaffey
2
@Terry Mahaffey Całkowicie zgadzam się z tobą, że to jest złe. Najlepiej zrobić to, co zasugerował Emile. Ale jeśli musisz zagwarantować, że opakowany kod nigdy nie zostanie wyrzucony, nie masz innego wyjścia, jak tylko umieścić zaczep (...) na dole wszystkich innych zidentyfikowanych zaczepów. Dzieje się tak, ponieważ opakowywana biblioteka może być słabo udokumentowana. Nie ma konstrukcji C ++, których można użyć w celu wymuszenia, że ​​może zostać zgłoszony tylko zestaw wyjątków. Jakie jest mniejsze zło? złapać (...) lub ryzykować awarię czasu wykonania, gdy opakowany kod próbuje przesłać do obiektu wywołującego C?
figurassa
1
catch (...) {std :: terminate (); } jest do zaakceptowania. catch (...) {} to potencjalna dziura w zabezpieczeniach
Terry Mahaffey
10

Nie jest trudno udostępnić kod C ++ w C, po prostu użyj wzorca projektowego Facade

Zakładam, że twój kod C ++ jest wbudowany w bibliotekę, wszystko, co musisz zrobić, to utworzyć jeden moduł C w swojej bibliotece C ++ jako fasadę do swojej biblioteki wraz z czystym plikiem nagłówkowym C. Moduł C wywoła odpowiednie funkcje C ++

Gdy to zrobisz, Twoje aplikacje C i biblioteka będą miały pełny dostęp do udostępnionego interfejsu API języka C.

na przykład tutaj jest przykładowy moduł Fasada

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

następnie ujawniasz tę funkcję C jako swoje API i możesz jej swobodnie używać jako biblioteki C bez martwienia się o

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

Oczywiście jest to wymyślony przykład, ale jest to najłatwiejszy sposób pokazania biblioteki C ++ w C

hhafez
źródło
Cześć @hhafez, czy masz prosty przykład Hello World? Jeden ze sznurkami?
Jason Foglia
dla faceta spoza CPP jest to cudowne
Nicklas Avén
6

Myślę, że możesz mieć kilka pomysłów na kierunek i / lub być może bezpośrednio wykorzystać SWIG . Myślę, że przejrzenie kilku przykładów dałoby przynajmniej wyobrażenie o tym, jakie rzeczy należy wziąć pod uwagę podczas pakowania jednego interfejsu API do drugiego. Ćwiczenie może być korzystne.

SWIG to narzędzie do tworzenia oprogramowania, które łączy programy napisane w C i C ++ z różnymi wysokopoziomowymi językami programowania. SWIG jest używany z różnymi typami języków, w tym z popularnymi językami skryptowymi, takimi jak Perl, PHP, Python, Tcl i Ruby. Lista obsługiwanych języków obejmuje również języki nieskryptowe, takie jak C #, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), Java, Lua, Modula-3, OCAML, Octave i R. Również kilka zinterpretowanych i skompilowanych implementacji Scheme ( Guile, MzScheme, Chicken). SWIG jest najczęściej używany do tworzenia interpretowanych lub kompilowanych środowisk programistycznych wysokiego poziomu, interfejsów użytkownika oraz jako narzędzie do testowania i prototypowania oprogramowania C / C ++. SWIG może również eksportować swoje drzewo parsowania w postaci wyrażeń XML i Lisp. SWIG może być swobodnie używany, rozpowszechniany,

harschware
źródło
2
SWIG jest po prostu ponad zabójstwem, jeśli wszystko, co chce zrobić, to udostępnić bibliotekę C ++ od C.
hhafez
1
To opinia i nie zawiera żadnych przydatnych informacji zwrotnych. SWIG pomógłby, gdyby oryginalny kod był: Zmienia się szybko, Nie ma zasobów C ++ do jego utrzymania i są dostępne tylko zasoby C, a programista chce zautomatyzować generowanie C API. Są to powszechne i na pewno ważne powody, dla których warto używać SWIG.
user1363990
5

Po prostu zastąp pojęcie obiektu znakiem void *(często określanym jako nieprzezroczysty typ w bibliotekach zorientowanych na C) i ponownie wykorzystaj wszystko, co znasz z C ++.

Hassan Syed
źródło
2

Myślę, że użycie SWIG jest najlepszą odpowiedzią ... nie tylko pozwala uniknąć ponownego wynalezienia koła, ale jest niezawodne, a także promuje ciągłość rozwoju, a nie rozwiązywanie problemu.

Problemy związane z wysoką częstotliwością wymagają rozwiązania długoterminowego.

danbo
źródło