Forum: Compiler & IDEs cast Zeiger auf Methode -> Zeiger auf Funktion


von tom (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Karsten Brandt (Gast)


Lesenswert?

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.

von Rolf Magnus (Gast)


Lesenswert?

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.

von tom (Gast)


Lesenswert?

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

von Karl heinz B. (kbucheg)


Lesenswert?

Der übliche Weg ist der, den Karsten aufgezeigt hat.

von Karl heinz B. (kbucheg)


Lesenswert?

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

von tom (Gast)


Lesenswert?

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

von Karsten Brandt (Gast)


Lesenswert?

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?

von Karsten Brandt (Gast)


Lesenswert?

Oder geht es darum, dass Du Methodenfunktionen der thread-Klasse in der
"globalen c-Thread-Funktion" benutzen willst?

von Karsten Brandt (Gast)


Lesenswert?

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\]

von Karl heinz B. (kbucheg)


Lesenswert?

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

von tom (Gast)


Lesenswert?

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

von tom (Gast)


Angehängte Dateien:

Lesenswert?

jetzt noch mal mit Code

von Karsten Brandt (Gast)


Lesenswert?

@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?

von Karl H. (kbuchegg)


Lesenswert?

Yep.

Löst aber nicht das generelle Problem:
Es gibt nur einen Pointer über den der Callback
geführt wird.

von Rolf Magnus (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

Richtig. Daran hab ich nicht mehr gedacht.

von tom (Gast)


Lesenswert?

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

von Karsten Brandt (Gast)


Lesenswert?

@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

von Karsten Brandt (Gast)


Lesenswert?

@Tom:

Sorry, ich meine natürlich die Virtualisierung der Execute Funktion!

von Karsten Brandt (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.