Hallo, ich bearbeite ein Projekt mit C-Bibliotheken/APIs und C++ Anwendung. Dabei habe ich das Problem, dass ich eine C-Funktion benutzen möchte, die als Parameter u.a. einen Funktionspointer vom Typ void (*)(long) hat. Dieser C-Funktion möchte ich nun als Parameter einen Pointer auf eine Klassenmethode vom Typ void (myclass::*)(long) mitgeben. Leider habe ich bisher keinen Weg gefunden, den Typ anzupassen. Ich alle möglichen Casts (reinterpret_cast usw.) probiert, aber es geht nicht. Kennt jemand einen Hack? Danke! Tom
Ich wüsste nicht, wie eine C-Funktion auf Elemente von C++- Klassen zugreifen können sollte -- und das müsste sie ja in diesem Fall. Du kannst nur einen generischen Zeiger durchreichen (als void *), der dann irgendwie wieder in einem C++-Code landet, der ihn entsprechend seiner tatsächlichen Klasse interpretiert.
Ich denke mal die sauberste Variante wäre der Einsatz einer Stub-Funktion. Ähnlich folgendem Beispiel: [C] void deine_Funktion(void* funcPtr) { //... } void stubFunc(long arg) { classType yourClass; // kann auch ein globales Klassenobjekt sein yourClass.classFunc(arg); } [\C] Den FunktionsPointer auf die Stub-Funktion kannst Du dann deiner Funktion übergeben. Ich denke mal das ist das, was Jörg meinte.
Es geht nicht. Ein Zeiger auf eine Memberfunktion und einer auf eine normale Funktion sind nicht kompatibel. Das geht ja schon alleine deshalb nicht, weil eine Memberfunktion automatisch noch den this-Zeiger implizit übergeben bekommt. Meistens ist ein Memberfunktionszeiger sogar ganz anders aufgebaut (es werden extra-Informationen mit abgespeichert), als ein normaler Funktionszeiger. Eine Konvertierung ist normalerwerise nicht möglich. Auch zwischen void* und Funktionszeigern oder void* und Memberfunktionszeigern ist zumindest laut C++-Norm eine Konvertierung nicht möglich (die meisten Compiler lassen erstere aber meistens doch zu). Kannst du den Code nicht in eine Nichtmemberfunktion schreiben, die extern "C" ist? Oft haben diese Callback-Mechanismen die Möglichkeit, einen void*-Parameter zu hinterlegen, der beim Aufruf der Funktion übergeben wird. Dort könntest du dann einen Zeiger auf dein Objekt übergeben, über den deine Callback-Funktion dann die Memberfunktion aufruft. PS: g++-spezifisch gibt's noch: http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Bound-member-functions.html#Bound-member-functions Aber das sollte nur verwendet werden, wenn es wirklich und mit Sicherheit absolut keinen anderen Weg gibt.
@Jörg Danke für die schnelle Antwort. Ich befürchte Du hast Recht. Ich habe gerade einen Versuch mit void* als "Zwischenstation" gemacht. Selbst der Cast von void (myclass::*)(long) nach void* geht nicht. Ich sollte mir lieber eine andere Implementierung ausdenken.
Der übliche Weg ist der, den Karsten aufgezeigt hat.
Wenn deine C-Funktion noch einen zusätzlichen void* Parameter neben dem Funktionszeiger akzeptiert, der dann an die Callback- Funktion weitergegeben wird, dann kannst du das ganze so machen. void CallbackStubForMyClass( void* Arg ) { ((MyClass*)Arg)->Callback(); } class MyClass { ... public void foo() { SomeFunction( CallbackStubForMyClass, this ); /* SomeFunction ruft dann über den Funktionszeiger die Funktion CallbackStubForMyClass auf, die ihrerseits dann die Member Funktion aufruft */ } public void Callback() { /* letztendlich landet der Call dann hier */ } }; Das geht aber nur deshalb, weil die Funktion SomeFunction 2 Pointer akzeptiert und es auf diese Art möglich wird, den this Pointer soweit durchzuschleifen, dass der CallbackStub ihn benutzen kann um darauf eine Member Funktion aufzurufen. Wenn dir SomeFunction aufs Aug gedrückt wurde und es keine Möglichkeit gibt, den this Pointer durchzuschleifen, dann bleibt nichts anderes üblich, als eine globale Pointer Variable zu benutzen, die diese Aufgabe übernimmt: MyClass* pCallbackObj; void CallbackStubForMyClass() { pCallbackObj->Callback(); } class MyClass { ... public void foo() { pCallbackObj = this; SomeFunction( CallbackStubForMyClass ); } public void Callback() { /* letztendlich landet der Call dann hier */ } };
@all vielen Dank für die rege Anteilnahme an meinem Problem! Ich glaube, ich muss ein bisschen mehr zu meinem Problem erklären. Ich schreibe eine Anwendung für einem ARM9 und habe dazu ein RTOS (ThreadX)als C-Bibliotheken (keine Sourcen) gegen die ich meine Anwendung linke. Ich habe mir eine Klasse thread geschrieben, die die C-Funktionen zur Threadhandhabung kappselt. Objekte die in einem eigenen Thread laufen sollen, werden von thread abgeleitet. Die Klasse enthält eine virtuelle Methode execute, die den Code enthält, der in dem Thread ausgeführt werden soll. execute wird in den abgeleiteten Klassen implementiert. thread::create erzeugt dann einen Thread mittels der C-Funktion create, die als Parameter die Funktion mit dem auszuführenden Code erwartet, in diesem Falle ist das thread::execute. Das hätte dann wohl zur Folge, dass ich alle von thread abgeleiteten Objekte global halten müßte und jedes mal wo ich ein solches Objekt erzeuge eine Stubfunktion schreiben müßte. Da muss ich mich wohl entscheiden, wo ich mein OO-Konzept durchbreche. @ Rolf Ich habe auch den Hinweis zu Bound-Member-Function gefunden und ausprobiert. Es unterdrückt den Cast-Fehler, dafür fehlen eben die Klasseninformationen (vtable) und es gibt später ein Linkerfehler. Also Zugriff der C-Funktion auf Klassenmethode, zumal virtuell, geht nicht.
Sorry, aber jetzt steh ich auf dem Schlauch. Wenn ich Dich richtig verstanden habe, willst Du eigentlich ein c-Funktion aus einer Methoden-Funktion aufrufen und nicht umgekehrt. Oder verstehe ich jetzt gar nichts mehr. Kannst Du evtl. Deine Sourcen zur Verfügung stellen, um Klarheit in die Sache zu bringen?
Oder geht es darum, dass Du Methodenfunktionen der thread-Klasse in der "globalen c-Thread-Funktion" benutzen willst?
Ich glaube jetzt hab ich's: Du willst die Member-Funktion thread::execute an die globale c-Thread-Funktion übergeben. Dies erreichst Du ganz einfach indem Du die Stub-Funktion als Member der thread-Klasse anlegst. Und diese muss für den globale Zugriff statisch sein. [C] // ich weiss nicht ob das hier weiterhilft, aber in einer // Windows-Umgebung nutze ich für diesen Zweck folgende Macro's #define DECLARE_THREAD_FUNCTION(className, functionName) \ static void functionName(void* classPtr) \ { \ ((className*)classPtr)->functionName(); \ } #define START_THREAD(functionName) _beginthread(functionName, 0, this) ///////////////////////////////////////////////////////////////// // Anwendungsbeispiel im Header deiner Thread-Klasse setzt Du vor der execute-Funktion folgendes Macro: DECLARE_THREAD_FUNCTION(thread, execute) // Dieses Macro erzeugt die statische Stub-Funktion. // In Deiner create-Funktion benutzt Du dann das: START_THREAD(execute) // Beachte dabei, dass Du das START_THREAD-Macros an deine Umgebung // anpassen musst. Es handelt sich in diesem Beispiel um Code für // die Windows-Umgebung! [C\]
> #define START_THREAD(functionName) \
_beginthread(functionName, 0, this)
****
Und hier wird der Knackpunkt liegen. Die wenigsten C-Bibliotheken,
die mit Callbacks arbeiten, haben die Möglichkeit zusätzlich zum
Funktionspointer noch weitere Argumente zur Callbackfunktion
durchzureichen.
@ Karsten Wirklich toller Einfall! Ich habe jetzt die Makros von dir ausprobiert. Der Compiler akzeptiert es, aber der Linker sagt leider "undefined reference to 'vtable of thread'". Schade! @all Ich habe noch ein Teil des Codes angehangen, falls jemand noch ein bischen Knobeln möchte. Ich werde heute Abend noch am Problem weitermachen, dann werde ich wohl das Problem umgehen müssen. Zum Code: Die Funktion applicationStart in root.cxx ist der Eintrittspunkt vom RTOS. thread ist die Klasse mit der Threadfunktionalität ( was auch sonst). cdci_control wird von thread abgeleitet und soll die Anwendung, die in diesem Thread laufen soll bereitstellen. Auf diese Art und Weise sollten alle Thread ihre Funktionalität bekommen. Ach so hier noch die Typdefinition von entry_function: typedef VOID (* entry_functionType)(ULONG entry_input);
@Karl Heinz: Um auf deine Variante zurück zukommem. Man könnte den MyClass-Pointer und die Stub-Funktion zu Membern der thread-Basis-Klasse machen und somit OO-kompatibel bleiben. Bsp:
1 | class thread |
2 | {
|
3 | static thread* pCallbackObj; |
4 | |
5 | static void StubForThreadExecution() |
6 | {
|
7 | pCallbackObj->execute(); |
8 | }
|
9 | ...
|
10 | |
11 | public void create() |
12 | {
|
13 | // setze Callback-Pointer
|
14 | pCallbackObj = this; |
15 | |
16 | // erzeuge thread mit create() aus ThreadX-OS
|
17 | ::create( StubForThreadExecution ); |
18 | }
|
19 | |
20 | public void execute() |
21 | {
|
22 | /* letztendlich landet der Call dann hier */
|
23 | }
|
24 | };
|
Könnte doch klappen oder?
Yep. Löst aber nicht das generelle Problem: Es gibt nur einen Pointer über den der Callback geführt wird.
> Man könnte den MyClass-Pointer und die Stub-Funktion zu Membern > der thread-Basis-Klasse machen und somit OO-kompatibel bleiben. Nein, eigentlich nicht. Funktionen, die aus C heraus aufgerufen werden, müssen als extern "C" deklariert sein. Das geht bei static-Memberfunktionen aber nicht. Meistens geht's trotzdem, aber korrekt ist es streng genommen nicht.
Vielen Dank für die vielen tollen und vor allem schnellen Tipps! Ich habe jetzt die CallbackObject-Variante probiert und die dabei wieder auftretenden Linker-Fehler untersucht. Dabei habe ich festgestellt, dass die virtuelle Deklaration von Execute die Ursache für die Linker-Fehler ist. Die virtuelle Deklaration ist aber eigentlich Unsinn, da ich ja das Objekt der abgeleiteten Klasse benutze. Das sollte also die Lösung sein! Ich werde es morgen gleich auf dem Targetsystem testen. Gute Nacht allerseits! Tom
@Karl Heinz: Du hast natürlich Recht, wenn Du an die Erzeugung von Thread-Objekten aus bereits laufenden Thread's denkst. Die Synchronisation ist nur für den Stub-Funktion notwendig. Ein bereits laufender Thread kennt seinen this* dann selbst. Denke ich jedenfalls. Aber für diesen Fall gibt es ja Synchronisationsobjekte wie Critical Sections oder Mutexe, um den Zugriff auf die static Ressource zu synchronisieren. @Tom: Wieso ist das mit der virtuellen Create() in der thread-Klasse Blödsinn? Wenn Du die thread-Klasse als Basisklasse für deine Threads nutzen willst ist die Virtualisierung der Create-Funktion mehr als sinnvoll. Um den Einwand von Karl Heinz zu berücksichtigen, führe in der Create() ein Synchronisationsobjekt vor dem Zugriff auf den CallbackPtr ein. z.B.
1 | public void create() |
2 | {
|
3 | // setze hier den Lock
|
4 | enterCriticalSection(&cs); // oder was Du zur Verfügung hast |
5 | |
6 | // setze Callback-Pointer
|
7 | pCallbackObj = this; |
8 | |
9 | // erzeuge thread mit create() aus ThreadX-OS
|
10 | ::create( StubForThreadExecution ); |
11 | |
12 | // gebe Lock wieder frei
|
13 | leaveCriticalSection(&cs); |
14 | }
|
15 | |
16 | // cs sollte in diesem Fall ein globales
|
17 | // Critical Section - Objekt sein
|
@Tom: Sorry, ich meine natürlich die Virtualisierung der Execute Funktion!
Ich hab mir mal den Header mit der Deklaration der tx_thread_create() besorgt. Die tx_thread_create() hat danach folgende Signatur: UINT tx_thread_create(TX_THREAD *thread_ptr, CHAR *name_ptr, VOID (*entry_function)(ULONG), ULONG entry_input, VOID *stack_start, ULONG stack_size, UINT priority, UINT preempt_threshold, ULONG time_slice, UINT auto_start); Die Signatur einer Thread-Funktion sieht so aus: void thread_func(ULONG thread_input) Mit anderen Worten man kann das Argument thread_input für das Durchreichen des this* missbrauchen. Um dann noch einmal auf meine Macro's zurückzukommen, die Stub-Funktion muss der Thread-Funktionssignatur entsprechen. Im Falle einer Stub-Funktion als Member der thread-Klasse ist diese als static zu deklarieren. Also so:
1 | static void stub_function(ULONG classPtr) |
2 | {
|
3 | ((className*)classPtr)->Execute(0); |
4 | }
|
Der Aufruf von tx_thread_create() müsste - um bei Deinem Code zu bleiben - dann folgendermaßen aussehen:
1 | unsigned int thread::Create(char* name_ptr, |
2 | unsigned long entry_input, |
3 | unsigned int priority, |
4 | unsigned int preempt_threshold, |
5 | unsigned long time_slice, |
6 | unsigned int auto_start) |
7 | {
|
8 | return tx_thread_create(thread_ptr, |
9 | name_ptr, |
10 | stub_function, |
11 | (ULONG)this, |
12 | stack_start, |
13 | stack_size, |
14 | priority, |
15 | preempt_threshold, |
16 | time_slice, |
17 | auto_start); |
18 | }
|
Ob das so funktioniert, habe ich jetzt aber nicht probiert! So kannst Du die Synchronistationsproblematik umgehen. Aber auf Kosten der Argumente bzw. Parameter für die Thread-Funktion.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.