//①從MakeObjectInstance(Method:TWndMethod):Pointer說起
MakeObjectInstance(Method:TWndMethod):Pointer
Begin
//構造一個4096大小的記憶體塊TInstanceBlock,如果該塊已滿,則建立一個新的4096大小的記憶體快以形成鏈表結構
//對於每一個4096大小的記憶體快TInstanceBlock,均由以下資料結構組成:
{
Next:指向下一個TInstanceBlock記憶體塊的指標
Code:彙編跳躍指令,跳轉到stdWndProc地址
WndProcPtr:指標
Instances:一個TObjectInstance類型的數組,數組的維度以填滿4096塊為止
}
//其中,對於每一個TObjectInstance類型的元素,均由以下資料結構組成:
{
Code: 和Offset一起組成一個彙編跳躍指令,跳轉到TInstanceBlock的Code地址(TInstanceBlock的Code地址中包換跳轉到stdWndProc地址的彙編指令)
Offset:
Integer:0代表下一個元素地址,1代表TWndMethod地址
}
//VCL把每一個TForm類或是控制項中處理視窗訊息方法指標放入到TObjectInstance中
//MakeObjectInstance函數最後返回的是TObjectInstance的地址,當成視窗回呼函數由Windows作業系統調用
//最後,通過TObjectInstance的彙編跳躍指令轉移到TInstanceBlock的Code地址,而TInstanceBlock的Code地址包含了跳轉到stdWndProc地址的彙編指令,最終,Windows作業系統通過MakeObjectInstance完成了從Windows訊息到stdWndProc的一個調用
End;
//②stdWndPro函數究竟做了些什麼?
1. 完成轉換Windows作業系統使用的stdcall調用慣例為register調用慣例以調用到類之中的訊息處理函數;
2. 將Self指標推入棧中;
3. 完成MainWndProc函數的調用,從而最終實現了從普通Window函數調用到類方法調用的轉換;
//③預定義的訊息類型
控制項訊息類的自訂訊息:CM_XXX;
控制項通知類的自訂訊息:CN_XXX;
其中
CN_XXX = CN_BASE + CM_YYY;//存在一定的預定計算關係
//④完整的訊息分配流程:以TButton的Click說起(假設Click事件觸發了WM_COMMOND訊息):
1. TButton的父類TButtonControl重載了父類TWinControl的WndProc方法,並在其中處理了部分訊息,未處理的訊息均通過inherited WndProc調用來轉交給TWinControl的WndProc方法;
2. TButton同時還重載了父類的CreateParams方法實現視窗類別資料結構內容的定義(通俗的說,就是實現了自己的自訂屬性的設定)
3. 由於TButton是屬於主表單的組件,因此視窗觸發的WM_COMMAND視窗訊息會調用主表單的MainWndProc函數來處理,這就是TWinControl.MainWndProc, TWinControl.MainWndProc會調用TForm.WndProc虛擬方法來處理視窗訊息, TForm.WndProc又會調用TCustomForm.WndProc來處理, TCustomForm.WndProc又調用TWinControl.WndProc處理,最後TWinControl.WndProc調用了TControl.WndProc(說明:繼承鏈使然);
4. 到了TControl.WndProc然後沒有處理WM_COMMAND訊息,因此TControl調用了TObject的訊息指派服務虛擬方法Dispatch來處理:
Procedure TControl.WndProc(var Message:TMessage)
Var
…
Dispatch(Message);
End;
5. TObject.Dispatch會在TForm1的動態方法表格中是否有方法可處理WM_COMMAND訊息,由於TForm1沒有定義處理WM_COMMAND訊息的動態方法,因此TObject.Dispatch會繼續到TForm1的父類的動態方法表格中搜尋,一直尋找到能夠處理WM_COMMAND視窗訊息的動態方法為止;
6. 最後,我們在TCustomForm中找到處理WM_COMMAND視窗訊息的動態方法WMCommand:
Procedure TCustomForm.WMCommand(var Message:TWMCommand);message WM_COMMAND;
…
Inherited;//調用父類TWinControl實現
…
End;
7. TWinCOntrol.WMCommand方法內容如下:
…
If not DoControlMsg(Message.Ctl,Message) then Inherited;
…
8. TWinCOntrol.DoControlMsg方法內容如下:
Function DoControlMsg(ControlHandle:HWnd;var Message):Boolean
…
Control := FindCOntrol(ControlHandle);//通過訊息系統找到訊息發送源(即TButton),如何找到的請參見⑤
…
With TMessage(Messgae) do
…
Control.Perform(Msg + CN_BASE,WParam,LParam);//調用TControl的Perform方法,請注意,通過CN_BASE的代數計算已完成從控制項訊息類的自訂訊息到控制項通知類的自訂訊息的轉換,詳情請參見③;
…
End;
9. TControl.Perform方法內容:
Function TControl.Perform(Msg:Cardinal;…)
…
WindowProc(Message);//請注意此時的訊息為CN_XXX;
…
10. 在TControl的建構函式中已經把虛擬方法的WndProc制定給了Perform函數中的WindowProc了,所以,Perform是調用了WndProc來處理視窗訊息;
11. 虛擬方法WindowProc會再經由TObject的Dispatch在組件中搜尋能夠處理此訊息的處理函數,我們再到TButton類中果然可以發現TButton定義了CNCommand方法來處理DoControlMsg發出的CN_COMMAND訊息(由步驟8轉換而來);
12. TButton.CNCommand的定義:
…
If Message.NotifyCode = BN_CLICKED then Click;
…
13. 到此已完成了,大家不用看就應該明白了,是的,正如你想到的,TControl中定義了動態方法Click,被TButton重載,在TButton.Click中,對其所在的表單的特性值進行了一個修改(具體什麼用尚不明確),然後就inherited Click;調用父類的實現了;
14. 最後,在TControl.Click中,檢查程式員是否已撰寫Onclick事件處理函數,如果已寫的話,就經由FOnclick調用程式員撰寫的代碼;
15. 當然了,TButton的執行個體Button1是這樣定義的:
Object Button1:TButton
…
Onclick = Button1Click
…
End
⑤如何在訊息系統中尋找一個對象的執行個體?
Function FindControl(Handle:HWnd):TWinControl
…
Result := ObjectFromHwnd(Handle);
…
End;
Function ObjectFromHwnd(Handle:HWnd):TWinControl
…
Result := Pointer(SendMessage(Handle,RM_GetObjectInstance,0,0));//通過SendMessage方法發送一個特定的訊息後得到一個指標並返回該地址
…
End;
Procedure TWinControl.DefaultHandler(var Message);//預設的訊息處理方法,不被顯式處理的訊息在此處理
…
If Msg = RM_GetObjectInstance then
Result := integer(Self);//經典吧?收到RM_GetObjectInstance這個訊息就返回自己的地址,這個自己當然是TWinControl繼承鏈中的任意某某了…
…
End;
全文完;