人人做人人澡人人爽欧美,国产主播一区二区,久久久精品五月天,羞羞视频在线观看免费

當前位置:蘿卜系統下載站 > 技術開發教程 > 詳細頁面

Delphi的消息機制

Delphi的消息機制

更新時間:2022-07-20 文章作者:未知 信息來源:網絡 閱讀次數:

  永遠記住,無論你是用 SDK 還是借用 VCL 來創建窗口,都要遵循 Windows 的游戲規則,即先注冊窗口類,然后再創建窗口實例,在消息循環中寫實現代碼。你還要知道 Windows 已經為了我們預注冊了多個窗口類,例如“Edit”、“ComboBox”,這時候我們要做的就是直接創建這些窗口,無需注冊窗口類了;在 Delphi 中這一切更簡單了,VCL 全部為你做好了,你只需簡單地在設計窗體上拖動你要的控件再寫實現代碼就可以了,是不是很 cool?
  一、窗口的創建

  VCL 中,具有句柄(Handle) 屬性的真正窗口控件全部繼承自 TWinControl,那就從 TWinControl 的創建開始說起。

  VCL 中窗口的建立不是按照我們想象中的流程創建的,即先把所有的窗口都創建好,然后再調用,而是在需要時才創建。可能你還不能理解我這句話的意思,慢慢看。繼承自 TWinControl 的窗口控件都會有 Handle 屬性,當代碼中需要 Handle 值時,通過該屬性的 getter 調用 TWinControl.HandleNeeded 來獲得句柄,這時如果窗體已經建立,直接返回句柄,否則先創建窗口實例,再返回句柄,因此窗口創建是在 TWinControl.HandleNeeded 中實現的。Borland 這樣做的目的我想是最大程度地來節省系統資源吧。

  TWinControl.HandleNeeded 中有幾個重要的方法,通過他們才得以創建窗口。TWinControl.HandleNeeded 調用TWinControl.CreateHandle 來獲得 Handle。但 CreateHandle 只是個包裝函數,它首先調用 TWinControl.CreateWnd 來創建窗口,CreateWnd 是一個重要的過程,它先調用 TWinControl.CreateParams 設置創建窗口的參數,通過這些參數調用 RegisterClass API 注冊窗口類,CreateWnd 然后調用 TWinControl.CreateWindowHandle,CreateWindowHandle 才是真正調用 CreateWindowEx API 創建窗口實例的函數。CreateHandle、CreateWnd、CreateParams、CreateWindowHandle都是虛方法,派生類可以重載這些方法以獲得更多的功能 ,其中 CreateParams 被重載的幾率最大。

  上面提到的方法源碼我建議你都要仔縛匆槐椋由鈑∠螅竺嫖姨岬降姆椒ǎ鬩捕家純叢綽耄芤嫖耷鈦劍醫輝傯崾盡?BR>
  至此一個窗口算是建立起來了,但是還是無法正確運行,因為它還沒有消息循環。

  二,消息循環的實現

  消息循環的實現是整個 VCL 消息框架中寫得最精彩的地方,因為傳統的 Windows 回調函數是一個靜態函數,而 VCL 中的窗體是類,調用類方法時,除了函數本身的地址,還需一個 Self,在它們之間建立關聯真不是一件容易的事情,需要大量的代碼技巧,同時消息循環還要保證每秒鐘能處理幾百到幾萬次的消息量,因此代碼更需要寫得精巧。 研習這部分代碼可能會花比較多的時間。

  我們知道注冊窗體類時就要提供窗體回調函數入口地址,那么可以想象到 VCL 中這個過程是發生在對 TWinControl.CreateWnd 的調用中,在該方法中,靜態函數指針 @InitWndProc 被賦值給 WNDCLASSEX 結構中的 lpfnWndProc,這是 VCl 窗體首次建立消息循環的地方。
InitWndProc 第一次被調用時,通過 SetWindowLong API 將消息回調函數替換成 TWindowControl.FObjectInstance,而TWinControl.FObjectInstance 就是一個普通的 Pointer,賦值是在 TWinControl.Create 中通過那個最具 Magic 的函數 MakeObjectInstance 完成的,這個過程非常復雜,詳細描述見參考[3]。

  替換的結果是類方法 TWinControl.MainWndProc 成為真正的消息處理 Handler,隨后的對應窗體實例的消息處理全部在 TWinControl.MainWndProc 中完成。其中還有一個細節就是消息在被 MainWndProc 處理之前還要調用一個純匯編寫的靜態函數 -- StdWndProc 將消息統一派發[1]。至此完成消息回調從普通的靜態函數到類方法的轉變。

  事實上 TWinControl.MainWndProc 是調用 WindowsProc 來實際處理窗口消息,在 TControl.Create 中 WindowsProc 是被指定成類中虛擬方法 WndProc。從 TControl 到實際的 VCL 窗體類這條繼承鏈上,很多派生類都重載了 WndProc,從而每個重載該方法的派生類都會增加一些功能。當然在繼承鏈的末端,例如 TForm,也可以重載 WndProc,來完成一些 tricky 代碼。記住,如果你重載 WndProc,總是先處理自己想要的消息,然后將不處理的消息遞交到父類的 WndProc 中處理。

  在每一個繼承類的 WndProc 中應該只處理維持窗體運作的最基本的消息,其他不處理的消息最終會在 TControl.WndProc 中被傳遞到 TObject.Diapatch。TObject.Diapatch 在自己和父類的動態方法表中查詢相應消息 ID,如果找到了,則調用相應的方法。所有處理消息的類方法都應該以關鍵字 message 定義,這可以保證其入口地址都是存在動態方法表中,從而也保證需要處理的消息 可以在 TObject.Diapatch 執行過程中被調用。

  如果在動態方表中還是無法查詢到需要處理的消息,那么 TObject.Diapatch 會繼續調用虛方法 DefaultHandler,TObject.DefaultHandler 只是個 PlaceHolder,該方法在 TWinControl 中被重載, TWinControl 繼承類中鮮有繼續重載該方法的類,可以認為消息最后一次被處理的機會就是發生在 TWinControl.DefaultHandler 中。我們知道在消息循環中不處理的消息最后都應該交給 Windows 的默認回調函數 DefWindowProc API 來處理, TWinControl.DefaultHandler 最主要的工作就是完成這個,除此之外,還完成幾個額外的消息處理[2]。

  VCL 的消息流程至此為止。

  可能你還在為整個消息分派流程犯暈,讓我用實例來分析一下吧。

  三、VCL 完整的消息分派流程

  1. TButton

  新建一個 Application,在 Form1 上放一個 Button (缺省名為Button1),在其 OnClick 事件中隨便寫點代碼,加上斷點,在調試之前,請打開 DCU 調試開關(Project->Options->Compiler->Use Debug DCUs), 這個開關如果不打開,是沒法調試 VCL 的,然后 F9 運行,當停留在斷點上時,打開Call Stack 窗口(View->Debug Window->Call Stack)可看到調用順序如下(從底往上看):

TForm1.Button1Click($9637C0)
TControl.Click
TButton.Click
TButton.CNCommand((48401, 660, 0, 524948, 0))
TControl.WndProc((48401, 660, 524948, 0, 660, 0, 660, 8, 0, 0))
TWinControl.WndProc((48401, 660, 524948, 0, 660, 0, 660, 8, 0, 0))
TButtonControl.WndProc((48401, 660, 524948, 0, 660, 0, 660, 8, 0, 0))
TControl.Perform(48401,660,524948)
DoControlMsg(524948,(no value))
TWinControl.WMCommand((273, 660, 0, 524948, 0))
TCustomForm.WMCommand((273, 660, 0, 524948, 0))
TControl.WndProc((273, 660, 524948, 0, 660, 0, 660, 8, 0, 0))
TWinControl.WndProc((273, 660, 524948, 0, 660, 0, 660, 8, 0, 0))
TCustomForm.WndProc((273, 660, 524948, 0, 660, 0, 660, 8, 0, 0))
TWinControl.MainWndProc((273, 660, 524948, 0, 660, 0, 660, 8, 0, 0))
StdWndProc(918056,273,660,524948)
TWinControl.DefaultHandler((no value))
TControl.WMLButtonUp((514, 0, 48, 13, (48, 13), 0))
TControl.WndProc((514, 0, 852016, 0, 0, 0, 48, 13, 0, 0))
TWinControl.WndProc((514, 0, 852016, 0, 0, 0, 48, 13, 0, 0))
TButtonControl.WndProc((514, 0, 852016, 0, 0, 0, 48, 13, 0, 0))
TWinControl.MainWndProc((514, 0, 852016, 0, 0, 0, 48, 13, 0, 0))
StdWndProc(524948,514,0,852016)
TApplication.HandleMessage
TApplication.Run
Project1

  一個 Button 被點擊,在 TButton 內部會發生兩個消息:WM_LBUTTONDOWN/WM_LBUTTONUP, TButton 沒有處理 WM_LBUTTONUP(問題:為什么只響應 WM_LBUTTONUP,這兩個消息只應該發生在 Windows 原生控件內,除非 TButton subclass 了 "Button",這部分代碼我沒看),只是交給 TWinControl.DefaultHandler,隨后 TButton 又將生成的 WM_COMMAND 消息發送給它的 Parent,即 TForm,經過一系列消息傳遞, WM_COMMAND 在 TWinControl.WMCommand 中被處理,通過 DoControlMsg 將 WM_COMMAND 加工成 CN_COMMAND,再利用 TControl.Perform 將 CN_COMMAND 傳回 TButton,又通過一系列的消息傳遞到 TButton 中的 Dispatch,通過查詢動態方法表找到 Handler -- TButton.CNCommand,它又調用虛方法 TButton.Click,繼而調用 TControl.Click,在這個方法中會調用 FOnClick,而 FOnClick 特性值的內容就是當程序員使用對象查看器撰寫 TButton 的 OnClick 事件處理函數時 Delphi 便會自動指定給 TButton 的 OnClick 特性,例子中 OnClick 被指定為 TForm1.Button1Click,因此 TForm1.Button1Click 最終被調用。

  2. TForm

  新建一個 Application,為 Form1 的 OnMouseDown 事件隨便寫一點代碼,在這個方法上設斷點,F9 運行,看看 Call Stack

TForm1.FormMouseDown(???,???,[ssLeft],346,212)
TControl.MouseDown(mbLeft,[ssLeft],346,212)
TControl.DoMouseDown((513, 1, 346, 212, (346, 212), 0),mbLeft,[])
TControl.WMLButtonDown((513, 1, 346, 212, (346, 212), 0))
TControl.WndProc((513, 1, 13893978, 0, 1, 0, 346, 212, 0, 0))
TWinControl.WndProc((513, 1, 13893978, 0, 1, 0, 346, 212, 0, 0))
TCustomForm.WndProc((513, 1, 13893978, 0, 1, 0, 346, 212, 0, 0))
TWinControl.MainWndProc((513, 1, 13893978, 0, 1, 0, 346, 212, 0, 0))
StdWndProc(2687598,513,1,13893978)
TApplication.HandleMessage
TApplication.Run
Project1

  鼠標在 Form 上點擊,產生兩個消息 WM_LBUTTONDOWN/WM_LBUTTONUP,但我們只截獲 WM_LBUTTONDOWN。產生的 WM_LBUTTONDOWN 經過一系列的消息傳遞到達 TObject.Dispatch,通過查詢動態方法表在 TForm 的父類 TControl 中找到了 Handler -- TControl.WMLButtonDown,在 TControl.WMLButtonDown 中又經過 TControl.DoMouseDown、TControl.MouseDown 一系列方法調用,最終調用到 FOnMouseDown,FOnMouseDown 被賦值為 TForm1.FormMouseDown,調用 FOnMouseDown 即調用 TForm1.FormMouseDown。
講了一大堆消息實現過程,那么在實際中到底有哪些應用?
  四、消息的實際應用

  如果你是共享軟件作者,經常會為你的軟件被 Crack 掉所煩惱,你能做的就是要加強你的軟件的 Anti-Crack 功能,今天就交你一招。

  如果你用過 Delphi 的專用反匯編工具 DEDE,那么你肯定知道像 Button1Click 這種 Event Handler 的方法入口地址 極容易被定位,其原理是根據TForm 的 RTTI 信息獲取的(通過分析 dfm 資源文件就可以獲得地址),其實 VCL 窗體只有 published 過的類成員才會生成 RTTI 信息。知道這個關鍵點加上對 VCL 消息機制的深入了解你就可以防止這一切發生。

  1. Anti-Crack

  新建一個 Application,在 Form1 上放兩個 Button,命名為 btnRegister、btnCancel,雙擊這兩個按鈕,分別生成TForm1.btnCancelClick、TForm1.btnRegisterClick 兩個 Event Handler 骨架代碼,然后在對象查看器中取消 btnRegister.OnClick 與 TForm1.btnRegisterClick 的關聯, 隨后將 TForm1.btnCancelClick 的聲明放入 TForms1 聲明的 private 區段。再按照下面的代碼 內容加入其他部分:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
btnRegister: TButton;
btnCancel: TButton;
procedure btnCancelClick(Sender: TObject);
private
procedure btnRegisterClick(Sender: TObject);
procedure WMCommand(var Message: TWMCommand); message WM_COMMAND;
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnCancelClick(Sender: TObject);
begin
Close;
end;

procedure TForm1.btnRegisterClick(Sender: TObject);
begin
ShowMessage('Thx for ur registration.');
end;

procedure TForm1.WMCommand(var Message: TWMCommand);
begin
if Message.NotifyCode = BN_CLICKED then
if FindControl(Message.Ctl) = btnRegister then
begin
btnRegisterClick(Self);
Exit;
end;
inherited;
end;

end.

  這個方法的本質就是截獲 TForm1 的 WM_COMMAND 消息并自己處理,請自行分析代碼,我就不多說了。編譯完后你可以用 DEDE 反匯編一下,看看還能不能那么容易地找到 TForm1.btnRegisterClick 的入口地址。

  結束語

  VCL 消息機制你理解了嗎?是不是感到特別復雜?一個消息往往要經過10幾個方法才能傳到 Event Handler,別看消息傳遞經過這么漫長的路途,但是 VCL 消息機制的效率還是非常高的,因為很多關鍵的代碼都是用匯編直接寫成的,每一個中途站花費的時間也非常少,因此需要處理的消息還是能很快地到達目的地。

  我最開始學 Windows 編程是從 SDK 開始學起的,那時候會寫了基本的 Windows 程序,一段時間內總認為會 SDK 比會用 Delphi 牛X,現在想起來真傻,比起直來直去的 SDK 編程,VCL 消息機制要復雜得多得多,看完 VCL 源碼后最大感受就是覺得以前跟沒學過編程似的,但不可否認的是,只有在你掌握了 OOP/ASM/SDK 這些基礎知識后,你才有看懂 VCL 源碼的資本,這些基礎知識你都掌握了嗎?

溫馨提示:喜歡本站的話,請收藏一下本站!

本類教程下載

系統下載排行

網站地圖xml | 網站地圖html
主站蜘蛛池模板: 灵宝市| 青神县| 鄂州市| 城步| 财经| 高清| 高淳县| 墨玉县| 肥东县| 寻乌县| 庆元县| 堆龙德庆县| 安泽县| 紫云| 云阳县| 乐安县| 佛教| 六盘水市| 瑞丽市| 延津县| 兴海县| 电白县| 满洲里市| 南宁市| 游戏| 阿鲁科尔沁旗| 江安县| 灌阳县| 株洲市| 陵川县| 临泉县| 连平县| 松潘县| 玉屏| 象州县| 沙洋县| 东光县| 昭苏县| 札达县| 淮阳县| 信宜市|