Mega Code Archive

 
Categories / Delphi / Examples
 

Convert method pointers into function pointers

// Converting method pointers into function pointers // Often you need a function pointer for a callback function. But what, if you want to specify a method as // an callback? Converting a method pointer to a function pointer is not a trivial task; both types are // incomatible with each other. Although you have the possibility to convert like this "@TClass.SomeMethod", // this is more a hack than a solution, because it restricts the use of this method to some kind of a class // function, where you cannot access instance variables. If you fail to do so, you'll get a wonderful gpf. // But there is a better solution: run time code generation! Just allocate an executeable memory block, and // write 4 machine code instructions into it: 2 instructions loads the two pointers of the method pointer // (code & data) into the registers, one calls the method via the code pointer, and the last is just a return // Now you can use this pointer to the allocated memory as a plain funtion pointer, but in fact you are // calling a method for a specific instance of a Class. // Methodenzeiger in Funktionszeiger umwandeln // Oft ist es nötig, einer API-Funktion einen Funktionszeiger, der auf eine Callbackfunktion zeigt, zu // übergeben. Leider ist ein Funktionszeiger mit Methodenzeigern inkompatibel, und so ist es zunächst // unmöglich, eine Methode als Callback anzugeben. // Man kann zwar einen Methodenzeiger auf die Art "@TKlasse.EineMethode" in einen Funktionszeiger casten, // allerdings darf man dann in dieser Methode dann auf keine Variablen (und auch auf Methoden, die diese // Variablen benutzen) der Klasse zugreifen, da diese immer Instanzgebunden sind und man in diesem Falle // keine Instanz angegeben hat (ähnlich einer Klassenmethode). // Es gibt aber auch noch einen besseren Weg, den Cast zu vollziehen, ohne irgendwelche Einschränkungen // hinzunehmen: // Ein Methodenzeiger ist im Grunde nichts anderes als ein record aus zwei Zeigern, einer zeigt auf die // Methode in der Klasse, der andere zeigt auf die Instanz, für die diese Methode aufgerufen werden soll. // Ein Methodenaufruf funktioniert in Delphi so: Es wird die Methode mithilfe des ersten Zeigers aufgerufen, // ganz wie ein normaler Funktionsaufruf. Allerdings wird der zweite Zeiger als "versteckter" Parameter // mitgegeben, er findet sich auch in der Variable "Self" wieder, die in jeder Methode zur Verfügung steht. // Jede Zuweisungen an Variablen der Instanz gehen über diesen Zeiger auf die Daten der Instanz. // Um nun eine Methode für eine bestimmte Instanz über einen "normalen" Funktionspointer aufzurufen, // kann man daher folgendes tun: Man legt einen ausführbaren Speicherbereich an, und schreibt in diesen // 4 Maschinencodeanweisungen: 2 davon enthalten die beiden Pointer (als Konstanten, die in ein Register // geschrieben werden, 1 den Aufruf der Methode, und 1 die Return-Anweisung. Den Zeiger auf den // Speicherbereich kann man nun als normalen Funktionspointer verwenden, der die Methode für eine ganz // bestimmte Instanz aufruft. // // Update 19.03.2003: // Nun kann diese Funktion jede Methode in eine entsprechende Funktion oder Prozedur // umwandeln. Es gelten allerdings folgende Einschränkungen: // - Die Methode MUSS als STDCALL deklariert sein. (Dies macht insofern Sinn, da alle // Windows-Apis auf diese Aufrukonvention verwenden.) // Der resultierende Pointer zeigt ebenso auf eine mit stdcall aufzurufende // Funktion oder Prozedur. // - NICHT kompatibel sind Methoden, deren Rückgabewert vom typ string, dynamic array, // method pointer, oder Variant ist. type TMyMethod = procedure of object; function MakeProcInstance(M: TMethod): Pointer; begin // allocate memory GetMem(Result, 15); asm // MOV ECX, MOV BYTE PTR [EAX], $B9 MOV ECX, M.Data MOV DWORD PTR [EAX+$1], ECX // POP EDX MOV BYTE PTR [EAX+$5], $5A // PUSH ECX MOV BYTE PTR [EAX+$6], $51 // PUSH EDX MOV BYTE PTR [EAX+$7], $52 // MOV ECX, MOV BYTE PTR [EAX+$8], $B9 MOV ECX, M.Code MOV DWORD PTR [EAX+$9], ECX // JMP ECX MOV BYTE PTR [EAX+$D], $FF MOV BYTE PTR [EAX+$E], $E1 end; end; procedure FreeProcInstance(ProcInstance: Pointer); begin // free memory FreeMem(ProcInstance, 15); end; // Am Ende sollte man natürlich nicht vergessen, den belegten Speicher auch wieder freizugeben. // "TMyMethod" kann man natürlich auch den Bedürfnissen entsprechend abändern, // z.B. mit Parametern für eine WindowProc versehen. // After all, you should not forget to release the allocated memory. // "TMyMethod" can be modified according your specific needs, e.g. add some parameters for a WindowProc. // N.B.: Yes, I know, Delphi has those "MakeProcInstance" function in its forms unit. // But this works a little bit different, has much more overhead, // and most important, you have to use the forms unit, which increases the size of your exe drastically, // if all other code doesn't use the VCL (e.g. in a fullscreen DirectX/OpenGl app). // Wer noch Fragen hat / if you have questions: Florian.Benz@t-online.de