構造一個通用的回調Thunk.(把回調函數指向對象的方法)
最近又看到了VCL代碼中的MakeObjectInstance函數,實際上是一段WndProc的Thunk代碼.再一次感歎VCL設計之精巧,效率之高.
不喜歡MFC的消息映射方式,MFC的消息映射雖然好理解,但是是采用查表方式效率實在是太低了.VCL的MakeObjectInstance可以
說是VCL Windows系統的靈魂所在,效率極高.
不禁想可不可以實現一個通用的回調函數Thunk呢,可以把所有回調函數都變成對象的方法.
但是MakeObjectInstance實際上是為WndProc特化的.
分析一下回調函數
1.回調函數不過是一個函數指針.
2.盡管回調函數可以是任何調用約定,但絕大多數Win32API的回調函數都是stdcall.(VC中WINAPI,PASCAL,CALLBACK不過是stdcall的宏).
我們完全可以不考慮其他的調用約定,只考慮stdcall的.
想一下,如果我們對象的方法也是一個stdcall調用約定的方法,那麼和回調函數還差什麼呢?
只差一個參數,第一個參數對象實例的指針,在Delphi,Pascal,Ada中叫Self,C++,Java,C#中叫this.VB中叫ME.
那麼我們只要塞給它這個對象的地址不就行了嗎.好在stdcall約定參數是由右向左傳遞的,也就是說第一個參數是最後傳遞的,又由於stdccall約定
參數全部是由棧傳遞的.所以我們只要把對象指針直接壓入棧中就行了.
但別忽略了一點,
call指令相當於
Push 返回地址
Jmp 函數
ret指令相當於
pop 返回地址
Jmp 返回地址
也就是說實際上在調用函數的時候棧頂保留的是返回地址,如果我們直接壓入實例指針的話原來,當跳到函數體中,函數會把返回地址當Self,而Self則
會被當成返回地址,具體會有什麼樣的後果大家自己去想像一下
所以我們做的事情就是彈出返回地址,壓入實例地址,壓入返回地址,跳到對象方法去執行.
實際上我們就是要構造這樣一段代碼當回調用,這段代碼插入對象實例參數到第一個參數,然後跳到對象方法:
pop eax //彈出返回地址到eax
push 對象實例 //壓入對象實例
push eax //壓入返回地址
jmp 對應的對象方法 //跳轉到相應的對象方法
具體實現如下
//構造出一段Thunk代碼
//構造出一段Thunk代碼
Function CreateThunk(Obj : TObject; CallBackProc: Pointer):Pointer;
const
PageSize = 4096;
SizeOfJmpCode = 5;
type
TCode = packed record
Int3: Byte; //想調試的的時候填Int 3($CC),不想調試的時候填nop($90)
PopEAX : Byte; //把返回地址從棧中彈出
Push: Byte; //壓棧指令
AddrOfSelf: TObject; //壓入Self地址,