...Convert method pointers into function pointers?

Author: Florian Benz

Category: Misc

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


 

printed from
www.swissdelphicenter.ch
developers knowledge base