Methodezeiger in Funktionenzeiger umwandeln?
-
Ich arbeite an einem Projekt, bei dem ich eine fremde API benutzen muss. Ich habe zwar die Möglichkeit diese API umzuschreiben, aber dies möchte ich wirklich nur im äußersten Notfall machen.
Nun zu meinem Problem. Von der API möchte ich eine Funktion aufrufen, die als Parameter einen Funktionenzeiger erwartet.
// Definition des Funtionenzeigers typedef int (*int_FctPtr)(const int&); // Zu benutzende Funktion der API void macheEtwas(int_FctPtr f, /*Weitere Parameter*/);Wenn ich in meinem Projekt eine globalle Funktion anlege mit obiger Signatur und diese der Funktion macheEtwas übergebe, funktionert alles bestens. Nun ist meine Funktion f aber von wechselnden Daten abhängig, die in einem Objekt gespeichert sind. Dieses Objekt kann ich aber der Funktion f nicht übergeben, da so deren Signatur geändert werden würde. Also dachte ich mir, man legt eine Instanzmethode im Objekt selber an, welche man dann übergibt.
class TestClass { private: double daten; public: TestClass(double); int f( const int& ); }; . . . typedef int (TestClass::*methode)(const int&); methode F = &TestClass::f; TestClass test(1.0); macheEtwas(test.*F, /*Weitere Parameter*/);Dies ergibt den Compilerfehler:
error C2664: 'macheEtwas': Konvertierung des Parameters 1 von 'überladener Funktionstyp' in 'int_FctPtr' nicht möglich
Der konvertierte Ausdruck ist nicht gültigMittlerweile weiß ich, dass Instanzmethoden und Funktionen eben doch nicht die gleiche Signatur haben, aber es muss doch möglich sein, einen Funktionenzeiger zu erzeugen und zu übergeben, wenn man das entsprechende Objekt im Hauptprogramm kennt.
Ich habe auch folgendes probiert:
TestClass param(0.0); int F( const int& x ) { return param.f(x); } . . . // Im Hauptprogramm TestClass test(1.0); param = test; macheEtwas(F, /*Weitere Daten*/);Das funktioniert zwar, aber ist quick und sowas von dirty, dass ich es gleich wieder verworfen habe.
Bevor die Frage aufkommen, ich benutze Visual C++ 2005 (daran gibt es leider nichts zu rütteln). Das Projekt beschäftigt sich unter anderem mit dem Lösen nichtlinearer Gleichungssysteme und dabei sollen nun die Werte wechselnder Funktionen an verschiedenen Stellen ausgewertet werden. Ich hoffe das verdeutlicht die Notwendigkeit von Funktions- und Methodenzeigern. Macht euch bitte keine Gedanken über den Datentyp. Ich habe int in obige Codeschnippseln aufgrund der Lesbarkeit gewählt.
Zu guter Letzt danke ich jedem, der etwas zu Lösung des Problems beitragen kann.
-
Jetzt mal nur ne anektdote zu methodenzeiger in funktionszeiger unwandeln:
Das hab ich früher immer mit inline assembler gemacht, und dann ecx entsprechend gesetzt...
-
@dfgdgdgf: Assembler klingt auch ziemlich brachial, aber da ich jeden Tipp begrüße, möchte ich dich bitten, das näher auszuführen.
-
nunja die funktion erwartet ja einen funtionszeiger, und ist auch fest, also die kannst du nicht mehr verändern. Die anketdote mit dem assembler, war für methoden, wo ich selber die memberfunktion aufrufe.
Für solche sachen ist deine lösung mit der hilfsfunktion am besten geeignet. Man kann nämlich nicht der aufgerufenen funktion sagen, dass sie die memberfunktion mit einem entsprechend gesetzten ecx aufrufen soll. Daraus folgt, dass wenn man mit assembler(oder sonstwie) den methodenzeiger übergibt, die funktion die zielfunktion aufruft, und dabei ecx nicht setzt. Und nun könnte eben die API intern ecx für irgeneine berechnung benutztnen, und dann zeigt ecx(respektive this) auf garnichts oder etwas, das nich vom typ TestClass ist.
-
Erstmal: Wenn irgendwer einen Funktionszeiger erwartet, kannst du ihm keinen Methodenzeiger übergeben - da fehlt der Bezug zum this-Objekt.
Wenn du die Arbeitsfunktion nicht ändern kannst, fällt mir eigentlich nur der Weg über eine Hilfsfunktion ein (in der Art, die du als "quick and dirty" eingestuft hast) - eventuell in leichten Variationen. (oder eventuell solche Assambler-Spielereien)
Wenn du eine Möglichkeit hast, die Arbeitsfunktion zu ändern, dann solltest du sie umstellen auf Funktoren.
-
Wer sagt überhaupt, dass auf seiner Plattform this in ecx übergeben wird?
Zum Thema: Sprachmittel gibt es da keine. Die Callback-Funktion erwartet nunmal nur einen Parameter, und eine passende Memberfunktion erwartet zwei (this implizit zusätzlich). Du kannst aber, wenn es nur ein Objekt gibt, zu dem die Methode aufgerufen wird, eine Hilfsfunktion schreiben (Achtung: Ansatz, nichts ausgereiftes)
Klasse* caller; int hilfsFunk(const int& x) { return caller->funk(x); } /* Aufruf */ caller = this; macheWas( &::hilfsFunk, /* ... */ );Von Assembler-Hacks würde ich grundsätzlich die Finger lassen.
-
Jetzt nich böse gemeint aber hast du seinen Post überhaupt durchgelesen?
class TestClass { private: double daten; public: TestClass(double); int f( const int& ); }; TestClass param(0.0); int F( const int& x ) { return param.f(x); } . . . // Im Hauptprogramm TestClass test(1.0); param = test; macheEtwas(F, /*Weitere Daten*/);Und das mit ecx war auf einem x86 OS auf eine x64 prozessor.
Und das natürlich nur bei __thiscall.Aber natürlich ist der o.g., oder dein Ansatz besser. Z.B. kann der x64 compiler kein inline assembler mehr, so ein mist...
-
ertetetee schrieb:
Jetzt nich böse gemeint aber hast du seinen Post überhaupt durchgelesen?
Nicht aufmerksam zumindest. Hab ich mir bei Unregs, deren Namen ich nichtmal aussprechen kann abgewöhnt. Hast aber Recht.
-
Naja, man könnte natürlich (je nach Signatur der Funktion) auch einfach die Adresse des Objekts übergeben und in der CallbackFunktion entsprechend zurück-Casten. Dazu muss man aber "beide Enden" unter Kontrolle haben und "die API" diesen Wert nur "durchschleusen".
Gruß,
Simon2.
-
Ich weiss nicht ob folgendes Funktioniert. Die Moeglichkeit der Hilfsfunktion wurd ja oben schon gegeben, allerdings hast du dann das Problem, dass du das Objekt, fuer das du die Methode aufrufen willst, in der Funktion fest angeben musst. Um das zu umgehen koennte ich mit folgendes vorstellen:
// Definition des Funtionenzeigers typedef int (*int_FctPtr)(const int&); // Zu benutzende Funktion der API void macheEtwas(int_FctPtr f, /*Weitere Parameter*/); class TestClass { private: double daten; public: TestClass(double); int f( const int& ); }; typedef int (T::funptr*)(const int&) template <TestClass* tcptr, funptr fun> int replace(const int& i) { return tcptr->funptr(i); } TestClass test(1.0); macheEtwas(&replace<&test, &TestClass::f>, /*Weitere Parameter*/);Vielleicht sind die operatoren nicht alle Korrekt, aber in der Art sollte es moeglich sein, das Objekt und die Funktion als template-parameter verpackt an die API zu verkaufen.
-
Also kurz gesagt: sauber geht es nicht.
2 Möglichkeiten wurden ja schon genannt:
-
Thunks (die "inline assembler" Version -- kann man aber auch machen indem man direkt die Code-Bytes in ein Array schreibt und den Zeiger auf das Array dann auf den entsprechenden Funktions-Zeiger-Typ castet). Natürlich muss man dazu dann den Instruction-Cache flushen, und natürlich die DEP (data execution prevention) beschwichtigen.
-
Globale Variablen bzw. "fest verdrahtete" Objekt-Zeiger. Auch doof, dafür einfacher und kein Ärger mit verschiedenen Plattformen (32 Bit, 64 Bit etc.).
Weitere möglichkeit weiss ich auch keine.
----
Wenn du dich für Thunks interessierst, in der ATL werden solche Thunks verwendet (um die WindowProc an das entsprechende Objekt weiterzuleiten).
Kannst du dir angucken wie das dort gemacht wird, und zwar in "atlstdthunk.h".
Die Klassen dort wirst du wahrscheinlich nicht 1:1 übernehmen können, sollte dir aber ne Vorstellung vermitteln wie man sowas macht.EDIT: das beste wäre sicher die API so zu ändern dass du beim Registrieren des Callbacks einen void* mitgeben kannst, den du dann als Parameter beim Callback wieder mitbekommst. Dann ist das Weiterleiten ein Kinderspiel, da du den void* z.B. direkt als "this" Zeiger verwenden kannst.
-