Möglichst einfach C Interface erstellen?
-
Hallo,
angenommen ich habe eine solche Klasse in C++:
struct A { int a, b, c; double x, y, z; int do_something(); // Viele weitere Werte/Funktionen };
und ich will für diese ein C Interface anbieten. Dann würde ich das so machen:
extern "C" { __declspec(dllexport) void* create_A() { return new A(); } __declspec(dllexport) void delete_A(void* object) { delete reinterpret_cast<*A>(object); } __declspec(dllexport) int get_a(void* object) { return reinterpret_cast<*A>(object)->a; } __declspec(dllexport) int set_a(void* object, int value) { reinterpret_cast<*A>(object)->a = value; } // etc. }
Funktioniert, ist aber aufwendig/nervig, vor allem wenn man viele Member in der jeweiligen Klasse hat. Geht das irgenwie besser oder automatisch?
-
Habs noch nicht gemacht; Fallstricke könnten also noch rumlungern.
struct A_Data { int a, b; double x, y; // viele weitere Werte }; struct A:A_Data { int c; double z; int do_something(); // Viele weitere Werte/Funktionen };
-
was ist denn an einer einzigen declspec-Zeile pro exportiertem getter oder setter aufwändig? Mit 0 Zeilen wird das kaum gehen.
Die Herstellung von Interfaces von C/C++ zu einigen anderen Sprachen läßt sich allerdings automatisieren (SWIG).
-
volkard schrieb:
Habs noch nicht gemacht; Fallstricke könnten also noch rumlungern.
struct A_Data { int a, b; double x, y; // viele weitere Werte }; struct A:A_Data { int c; double z; int do_something(); // Viele weitere Werte/Funktionen };
Hm, ich glaub ich versteh den Vorschlag nicht ganz... Wie genau kann mir die Indirektion über Vererbung hier helfen?
klassenmethode schrieb:
was ist denn an einer einzigen declspec-Zeile pro exportiertem getter oder setter aufwändig? Mit 0 Zeilen wird das kaum gehen.
Die Herstellung von Interfaces von C/C++ zu einigen anderen Sprachen läßt sich allerdings automatisieren (SWIG).
Ok, SWIG kannte ich noch nicht, werde ich mir mal anschauen.
-
happystudent schrieb:
volkard schrieb:
Habs noch nicht gemacht; Fallstricke könnten also noch rumlungern.
struct A_Data { int a, b; double x, y; // viele weitere Werte }; struct A:A_Data { int c; double z; int do_something(); // Viele weitere Werte/Funktionen };
Hm, ich glaub ich versteh den Vorschlag nicht ganz... Wie genau kann mir die Indirektion über Vererbung hier helfen?
A_Data kannste dem C-Compiler auch geben.
Die aus C++ angebotenen "extern C"-Funktionen casten sich den übergebenen A-Data* nach A* und hantieren ganz normal.
-
übrigens kann man sich die Routine-Arbeit bei Wrappern als Interfaces schön vereinfachen, indem man member function pointers benutzt und für jeden Getter- und Setter-Type nur noch einen generischen Wrapper Implementiert und exportiert. Im Idealfall noch den generischen Wrapper polymorph ausführen oder gar templatisieren.
-
klassenmethode schrieb:
übrigens kann man sich die Routine-Arbeit bei Wrappern als Interfaces schön vereinfachen, indem man member function pointers benutzt und für jeden Getter- und Setter-Type nur noch einen generischen Wrapper Implementiert und exportiert. Im Idealfall noch den generischen Wrapper polymorph ausführen oder gar templatisieren.
wie funktioniert das bitte?
in wie weit helfen mir die member function pointer.
den kann ich doch von C gar nicht aufrufen, so ganz ohne Member instance pointer und abenteuelerliche syntax.
hast du ein Beispiel damit ich versteh wie das aussehen soll?
-
statt jeden Getter einzeln zu exportieren, für jeden (C-)Rückgabetyp T nur eine Funktion ins extern "C" Interface exportieren, sagen wir: int gen_get(T* arg, int OBJECT_ID, int GETTER_ID){ ... }
In der class-spezifischen Schicht dann zwei Abbildungen, nämlich eine "obj", die OBJECT_ID auf die Adresse des Zielobjekts abbildet, und eine, die GETTER_ID auf einen member function pointer abbildet. Fehlercode rückgeben, wenn GETTER_ID ungültig.
Dann auf das Objekt obj(OBJECT_ID) den member fct pointer GETTER_ID mit Argument arg anwenden.
Die OBJECT_ID-Abbildung läßt sich sparen, wenn man statt ID die Adresse des Objekts benutzt, wie es der Thread-Opener macht (wen das void*-Casting nicht stört): int gen_get(T* arg, void* OBJECT_ADR, int GETTER_ID){ ... }
-
klassenmethode schrieb:
statt jeden Getter einzeln zu exportieren, für jeden (C-)Rückgabetyp T nur eine Funktion ins extern "C" Interface exportieren, sagen wir: int gen_get(T* arg, int OBJECT_ID, int GETTER_ID){ ... }
In der class-spezifischen Schicht dann zwei Abbildungen, nämlich eine "obj", die OBJECT_ID auf die Adresse des Zielobjekts abbildet, und eine, die GETTER_ID auf einen member function pointer abbildet. Fehlercode rückgeben, wenn GETTER_ID ungültig.
Dann auf das Objekt obj(OBJECT_ID) den member fct pointer GETTER_ID mit Argument arg anwenden.
Die OBJECT_ID-Abbildung läßt sich sparen, wenn man statt ID die Adresse des Objekts benutzt, wie es der Thread-Opener macht (wen das void*-Casting nicht stört): int gen_get(T* arg, void* OBJECT_ADR, int GETTER_ID){ ... }
danke für die Erklärung,
ich sehe jedoch weder Vereinfachung noch weniger Code,anstatt einer Zeile export hab ich
die Deklaration der GETTER_ID, was sicher alleine gesehen geringfügig kürzer sein mag
plus intern muss ich das Paar GETTER_ID + Member Funcktions Pointer in eine extra dafür erzeugte Key Value Container welchen Typs auch immer legen.
plus der extra erzeugten Funktionen zum returnierenalso ich seh da eher wenig bis keine Vorteile, dafür zusätzliche runtime Komplexität + Objekt grösse, der frage was mach ich wenn eine ID nicht existiert, ....
der Ansatz mag meiner Meinung nach gut sein für eine reflection ähnliche Sache, aber ansonst seh ich eher zusätzliche Komplexität und keinerlei Vereinfachung um ein C Interface zu erzeugen.
Da erscheint mir die Trennung Datan Struct Funktionsklasse doch mehr geeignet und sauberer.
Da stellt sich dann nämlich gleich auch die Frage, muss in C++ alles in eine Klasse oder darfs auch mal eine freie Funktion geben.
-
hängt vom Einsatzfall ab. Zur Anbindung einer Lib an eine andere Sprache braucht man oft für jede exportierte Funktion boilerplate code, der die Argumente in das Format umsetzt, das die Zielsprache benötigt, muß evtl Speicher reservieren und Daten transferieren in Speicherbereiche, die von der Zielsprache verwaltet werden usw. Da kann sich ein solcher Dispatcher lohnen.
-
Ok, danke für die Vorschläge.
Ich werd es mal mit dem Daten-Struct per Vererbung versuchen (und mir parallel SWIG anschauen).
Einen extra Container zum managen der Instanzen würde ich nur ungerne zwischenschalten, weil das doch auf die Performance gehen könnte.