




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
1、自網(wǎng)上轉(zhuǎn)載我從去年 12 月上旬開始等待李維的 Inside VCL 。我當(dāng)時(shí)的計(jì)劃是,在這本 書的指導(dǎo)下深入學(xué)習(xí) Delphi。到了 12 月底,書還沒有出來,我不愿再等,開 始閱讀 VCL 源代碼。在讀完 TObject、 TPersistant 和 TComponent 的代碼之 后,我發(fā)現(xiàn)還是不清楚 Delphi 對象到底是怎樣被創(chuàng)建的。于是我查看 Delphi 生成的匯編代碼, 終于理解了對象創(chuàng)建的整個(gè)過程 (這里要特別感謝 book523 的 幫助 。此后我就開始學(xué)習(xí) Delphi VCL 的消息處理機(jī)制。自從我寫下 Delphi 的對象 機(jī)制淺探,至今正好一個(gè)星期,我也基本上把
2、Delphi VCL 的消息處理框架讀 完了。 我的學(xué)習(xí)方法就是閱讀源代碼, 一開始比較艱苦, 后來線索逐漸清晰起來。 在此把自己對 Delphi VCL 消息機(jī)制的理解記錄下來,便于今后的復(fù)習(xí),也給初 學(xué) Delphi 或沒有時(shí)間閱讀 VCL 源代碼的朋友參考 (畢竟沒有幾個(gè)程序員像我 這樣有時(shí)間 。由于學(xué)習(xí)時(shí)間較短,一定會(huì)有錯(cuò)誤,請大家指正。我在分析 VCL 消息機(jī)制的過程中,基本上只考查了三個(gè)類 TObject、 TControl 和 TWinControl。 雖然我沒有閱讀上層類 (如 TForm的代碼, 但我認(rèn)為這些都是 實(shí)現(xiàn)的細(xì)節(jié)。我相信 VCL 消息系統(tǒng)中最關(guān)鍵的東西都在這三個(gè)類
3、中。綱舉而目 張,掌握基礎(chǔ)類的消息處理方法之后再讀其他類的消息處理過程就容易得多了。要想讀懂本文,最低配置為:了解 Win32 消息循環(huán)和窗口過程基本了解 TObject、 TControl 和 TWinControl 實(shí)現(xiàn)的內(nèi)容熟悉 Delphi 對象的重載與多態(tài)推薦配置為:熟悉 Win32 SDK 編程熟悉 Delphi 的對象機(jī)制熟悉 Delphi 內(nèi)嵌匯編語言推薦閱讀: Delphi 的原子世界 VCL 窗口函數(shù)注冊機(jī)制研究手記,兼與 MFC 比較 Delphi 的對象機(jī)制淺探本文排版格式為:正文由窗口自動(dòng)換行; 所有代碼以 80 字符為邊界; 中英文字符以空格符分 隔。(作者保留對本
4、文的所有權(quán)利,未經(jīng)作者同意請勿在在任何公共媒體轉(zhuǎn)載。 目 錄= = 一個(gè) GUI Application 的執(zhí)行過程:消息循環(huán)的建立 TWinControl.Create、注冊窗口過程和創(chuàng)建窗口 補(bǔ)充知識:TWndMethod 概述 VCL 的消息處理從 TWinControl.MainWndProc 開始 TWinControl.WndProc TControl.WndProc TObject.Dispatch TWinControl.DefaultHandler TControl.Perform 和 TWinControl.Broadcast TWinControl.WMPaint 以 T
5、WinControl 為例描述消息傳遞的路徑= =正 文= = 一個(gè) GUI Application 的執(zhí)行過程:消息循環(huán)的建立= =通常一個(gè) Win32 GUI 應(yīng)用程序是圍繞著消息循環(huán)的處理而運(yùn)行的。在一個(gè)標(biāo)準(zhǔn) 的 C 語言 Win32 GUI 程序中,主程序段都會(huì)出現(xiàn)以下代碼:while (GetMessage(&msg, NULL, 0, 0 / GetMessage 第二個(gè)參數(shù)為 NULL, / 表示接收所有應(yīng)用程序產(chǎn)生的窗 口消息TranslateMessage(&msg; / 轉(zhuǎn)換消息中的字符集DispatchMessage(&msg; / 把 msg 參數(shù)傳遞給 lpfnWnd
6、Proc lpfnWndProc 是 Win32 API 定義的回調(diào)函數(shù)的地址,其原型如下:int _stdcall WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam;Windows 回 調(diào) 函 數(shù) (callback function 也 通 常 被 稱 為 窗 口 過 程 (window procedure ,本文隨意使用這兩個(gè)名稱,代表同樣的意義。應(yīng)用程序使用 GetMessage 不斷檢查應(yīng)用程序的消息隊(duì)列中是否有消息到達(dá)。 如 果發(fā)現(xiàn)了消息, 則調(diào)用 TranslateMessage。 TranslateMessag
7、e 主要是做字符消息本地化的工作, 不是 關(guān) 鍵的函 數(shù) 。然后調(diào)用 DispatchMessage(&msg。 DispatchMessage(&msg 使 用 msg 為 參 數(shù) 調(diào) 用 已 創(chuàng) 建 的 窗 口 的 回 調(diào) 函 數(shù) (WndClass.lpfnWndProc。 lpfnWndProc 是由用戶設(shè)計(jì)的消息處理方法。當(dāng) GetMessage 在 應(yīng) 用 程 序 的 消 息 隊(duì) 列 中 發(fā) 現(xiàn) 一 條 WM_QUIT 消 息 時(shí) , GetMessage 返回 False,消息循環(huán)才告結(jié)束,通常應(yīng)用程序在這時(shí)清理資源后 也結(jié)束運(yùn)行。使用最原始的 Win32 API 編寫的應(yīng)用程序
8、的執(zhí)行過程是很容易理解的,但是用 Delphi VCL 組件封裝消息系統(tǒng),并不是容易的事。首先, Delphi 是一種面向?qū)?象的程序設(shè)計(jì)語言,不但要把 Win32 的消息處理過程封裝在對象的各個(gè)繼承類 中,讓應(yīng)用程序的使用者方便地調(diào)用,也要讓 VCL 組件的開發(fā)者有拓展消息處 理的空間。 其次, Delphi 的對象模型中所有的類方法都是對象相關(guān)的 (也就是傳 遞了一個(gè)隱含的參數(shù) Self,所以 Delphi 對象的方法不能直接被 Windows 回 調(diào)。 Delphi VCL 必須用其他的方法讓 Windows 回調(diào)到對象的消息處理函數(shù)。讓我們跟蹤一個(gè)標(biāo)準(zhǔn)的 Delphi Applicat
9、ion 的執(zhí)行過程, 查看 Delphi 是如何 開始一個(gè)消息循環(huán)的。program Project1;beginApplication.Initialize;Application.CreateForm(TForm1, Form1;Application.Run;end.在 Project1 的 Application.Initialize 之前, Delphi 編譯器會(huì)自動(dòng)插入一 行代碼:SysInit._InitExe。 _InitExe 主要是初始化 HInstance 和模塊信息表等。 然后 _InitExe 調(diào)用 System._StartExe。 System._StartExe
10、 調(diào)用 System.InitUnit; System.InitUnit 調(diào)用項(xiàng)目中所有被包含單元的 Initialization 段的代碼; 其 中有 Controls.Initialization 段,這個(gè)段比較關(guān)鍵。在這段代碼中建立了 Mouse 、 Screen 和 Application 三個(gè)關(guān)鍵的全局對象。Application.Create 調(diào)用 Application.CreateHandle。 Application.CreateHandle 建立一個(gè)窗口, 并設(shè)置 Application.WndProc 為回 調(diào)函數(shù) (這里使用了 MakeObjectInstance 方法
11、,后面再談 。 Application.WndProc 主要處理一些應(yīng)用程序級別的消息。我第一次跟蹤應(yīng)用程序的執(zhí)行時(shí)沒有發(fā)現(xiàn) Application 對象的創(chuàng)建過程,原來 在 SysInit._InitExe 中被隱含調(diào)用了。 如果你想跟蹤這個(gè)過程, 不要設(shè)置斷點(diǎn), 直接按 F7 就發(fā)現(xiàn)了。然后才到了 Project1 的第 1 句: Application.Initialize;這個(gè)函數(shù)只有一句代碼:if InitProc nil then TProcedure(InitProc;也就是說如果用戶想在應(yīng)用程序的執(zhí)行前運(yùn)行一個(gè)特定的過程,可以設(shè)置 InitProc 指向該過程。 (為什么用戶不
12、在 Application.Initialize 之前或在單 元的 Initliazation 段中直接運(yùn)行這個(gè)特定的過程呢?一個(gè)可能的答案是:如 果元件設(shè)計(jì)者希望在應(yīng)用程序的代碼執(zhí)行之前執(zhí)行一個(gè)過程, 并且這個(gè)過程必須 在其他單元的 Initialization 執(zhí)行完成之后執(zhí)行 比如說 Application 對象 必須創(chuàng)建 ,則只能使用這個(gè)過程指針來實(shí)現(xiàn)。 然后是 Project1 的第 2 句: Application.CreateForm(TForm1, Form1; 這句的主要作用是創(chuàng)建 TForm1 對象,然后把 Application.MainForm 設(shè)置為 TForm1。最
13、后是 Project1 的第 3 句: Application.Run;TApplication.Run 調(diào) 用 TApplication.HandleMessage 處 理 消 息 。 Application.HandleMessage 的代碼也只有一行:if not ProcessMessage(Msg then Idle(Msg;TApplication.ProcessMessage 才真正開始建立消息循環(huán)。 ProcessMessage 使 用 PeekMessage API 代 替 GetMessage 獲 取 消 息 隊(duì) 列 中 的 消 息 。 使 用 PeekMessage 的好
14、處是 PeekMessage 發(fā)現(xiàn)消息隊(duì)列中沒有消息時(shí)會(huì)立即返回, 這樣就為 HandleMessage 函數(shù)執(zhí)行 Idle(Msg 提供了依據(jù)。ProcessMessage 在處理消息循環(huán)的時(shí)候還特別處理了 HintMsg、 MDIMsg 、 KeyMsg 、 DlgMsg 等特殊消息,所以在 Delphi 中很少再看到純 Win32 SDK 編程 中的要區(qū)分 Dialog Window 、 MDI Window 的處理,這些都被封裝到 TForm 中去 了 (其實(shí) Win32 SDK 中的 Dialog 也是只是 Microsoft 專門寫了一個(gè)窗口過程 和一組函數(shù)方便用戶界面的設(shè)計(jì),其內(nèi)
15、部運(yùn)作過程與一個(gè)普通窗口無異 。function TApplication.ProcessMessage(var Msg: TMsg: Boolean;varHandled: Boolean;beginResult := False;if PeekMessage(Msg, 0, 0, 0, PM_REMOVE then / 從消息隊(duì)列獲取消息 beginResult := True;if Msg.Message WM_QUIT thenbeginHandled := False; / Handled 表示 Application.OnMessage 是否已 /經(jīng)處理過當(dāng)前消息。/ 如果用戶設(shè)置
16、了 Application.OnMessage 事件 /句柄,則先調(diào)用 Application.OnMessage if Assigned(FOnMessage then FOnMessage(Msg, Handled;if not IsHintMsg(Msg and not Handled and not IsMDIMsg(Msg and not IsKeyMsg(Msg and not IsDlgMsg(Msg then/ 思考:not Handled 為什么不放在最前? beginTranslateMessage(Msg; / 處理字符轉(zhuǎn)換DispatchMessage(Msg; /調(diào)用
17、 WndClass.lpfnWndProc end;endelseFTerminate := True; / 收到 WM_QUIT 時(shí)應(yīng)用程序終止 / (這里只是設(shè)置一個(gè)終止標(biāo)記 end;end;從上面的代碼來看, Delphi 應(yīng)用程序的消息循環(huán)機(jī)制與標(biāo)準(zhǔn) Win32 C 語言應(yīng)用 程序差不多。 只是 Delphi 為了方便用戶的使用設(shè)置了很多擴(kuò)展空間, 其副作用 是消息處理會(huì)比純 C Win32 API 調(diào)用效率要低一些。= = TWinControl.Create、注冊窗口過程和創(chuàng)建窗口= =上面簡單討論了一個(gè) Application 的建立到形成消息循環(huán)的過程,現(xiàn)在的問題 是 Delp
18、hi 控件是如何封裝創(chuàng)建窗口這一過程的。 因?yàn)橹挥薪⒘舜翱? 消息循 環(huán)才有意義。讓我們先回顧 Delphi VCL中幾個(gè)主要類的繼承架框:TObject 所有對象的基類TPersistent 所有具有流特性對象的基類TComponent 所有能放在 Delphi Form Designer 上的對象的基類 TControl 所有可視的對象的基類TWinControl 所有具有窗口句柄的對象基類Delphi 是從 TWinControl 開始實(shí)現(xiàn)窗口相關(guān)的元件。 所謂窗口, 對于程序設(shè)計(jì) 者來說, 就是一個(gè)窗口句柄 HWND。 TWinControl 有一個(gè) FHandle 私有成員代表 當(dāng)
19、前對象的窗口句柄,通過 TWinControl.Handle 屬性來訪問。我第一次跟蹤 TWinControl.Create 過程時(shí),竟然沒有發(fā)現(xiàn) CreateWindow API 被調(diào)用, 說明 TWinControl 并不是在對象創(chuàng)建時(shí)就建立 Windows 窗口。 如果用 戶使用 TWinControl.Create(Application 以后,立即使用 Handle 訪問窗口會(huì)出現(xiàn)什么情況呢?答案在 TWinControl.GetHandle 中, Handle 是一個(gè)只讀的窗口句柄:property TWinControl.Handle: HWnd read GetHandle;T
20、WinControl.GetHandle 代碼的內(nèi)容是:一旦用戶要訪問 FHandle 成員, TWinControl.HandleNeeded 就 會(huì) 被 調(diào) 用 。 HandleNeeded 首 先 判 斷 TWinControl.FHandle 是否是等于 0 (還記得嗎?任何對象調(diào)用構(gòu)造函數(shù)以后 所有對象成員的內(nèi)存都被清零 。 如果 FHandle 不等于 0, 則直接返回 FHandle; 如果 FHandle 等于 0,則說明窗口還沒有被創(chuàng)建,這時(shí) HandleNeeded 自動(dòng)調(diào) 用 TWinControl.CreateHandle 來創(chuàng)建一個(gè) Handle。但 CreateHa
21、ndle 只是個(gè) 包裝函數(shù),它首先調(diào)用 TWinControl.CreateWnd 來創(chuàng)建窗口,然后生成一些維 護(hù) VCL Control 運(yùn)行的參數(shù) (我還沒細(xì)看 。 CreateWnd 是一個(gè)重要的過程,它 先調(diào)用 TWinControl.CreateParams 設(shè)置創(chuàng)建窗口的參數(shù)。 (CreateParams 是個(gè) 虛方法, 也就是說程序員可以重載這個(gè)函數(shù), 定義待建窗口的屬性。 CreateWnd 然后調(diào)用 TWinControl.CreateWindowHandle。 CreateWindowHandle 才是真正調(diào) 用 CreateWindowEx API 創(chuàng)建窗口的函數(shù)。夠麻煩
22、吧,我們可以抱怨 Borland 為什么把事情弄得這么復(fù)雜,但最終希望 Borland 這樣設(shè)計(jì)自有它的道理。上面的討論可以總結(jié)為 TWinControl 為了為 了減少系統(tǒng)資源的占用盡量推遲建立窗口, 只在某個(gè)方法需要調(diào)用到控件的窗口 句柄時(shí)才真正創(chuàng)建窗口。 這通常發(fā)生在窗口需要顯示的時(shí)候。 一個(gè)窗口是否需要 顯示常常發(fā)生在對 Parent 屬性 (在 TControl 中定義 賦值的時(shí)候。設(shè)置 Parent 屬性時(shí), TControl.SetParent 方法會(huì)調(diào)用 TWinControl.RemoveControl 和 TWinControl.InsertControl 方 法 。 In
23、sertControl 調(diào) 用 TWinControl.UpdateControlState 。 UpdateControlState 檢 查 TWinControl.Showing 屬性來判斷是否要調(diào)用 TWinControl.UpdateShowing。 UpdateShowing 必須要有一個(gè)窗口句柄,因此調(diào)用 TWinControl.CreateHandle 來創(chuàng)建窗口。不過上面說的這些,只是繁雜而不艱深,還有很多關(guān)鍵的代碼沒有談到呢。你可能發(fā)現(xiàn)有一個(gè)關(guān)鍵的東西被遺漏了,對,那就是窗口的回調(diào)函數(shù)。由于 Delphi 建立一個(gè)窗口的回調(diào)過程太復(fù)雜了 (并且是非常精巧的設(shè)計(jì) ,只好單獨(dú) 拿
24、出來討論。cheka 的 VCL 窗口函數(shù)注冊機(jī)制研究手記,兼與 MFC 比較一文中對 VCL 的 窗 口 回 調(diào) 實(shí) 現(xiàn) 進(jìn) 行 了 深 入 的 分 析 , 請 參 考 : 我在此簡單介紹回調(diào)函數(shù)在 VCL 中的實(shí)現(xiàn):TWinControl.Create 的代碼中,第一句是 inherited,第二句是FObjectInstance := Classes.MakeObjectInstance(MainWndProc;我想這段代碼可能嚇倒過很多人,如果沒有 cheka 的分析,很多人難以理解。 但是你不一定真的要閱讀 MakeObjectInstance 的實(shí)現(xiàn)過程,你只要知道:MakeObj
25、ectInstance 在內(nèi)存中生成了一小段匯編代碼,這段代碼的內(nèi)容就是一 個(gè)標(biāo)準(zhǔn)的窗口過程。 這段匯編代碼中同時(shí)存儲了兩個(gè)參數(shù), 一個(gè)是 MainWndProc 的地址,一個(gè)是 Self (對象的地址 。這段匯編代碼的功能就是使用 Self 參數(shù) 調(diào)用 TWinControl.MainWndProc 函數(shù)。MakeObjectInstance 返 回 后 , 這 段 代 碼 的 地 址 存 入 了 TWinControl.FObjectInstance 私有成員中。這樣, TWinControl.FObjectInstance 就可以當(dāng)作標(biāo)準(zhǔn)的窗口過程來用。 你可能 認(rèn)為 TWinContr
26、ol 會(huì)直接把 TWinControl.FObjectInstance 注冊為窗口類的 回 調(diào) 函 數(shù) (使 用 RegisterClass API , 但 這 樣 做 是 不 對 的 。 因 為 一 個(gè) FObjectInstance 的匯編代碼內(nèi)置了對象相關(guān)的參數(shù) (對象的地址 Self,所以 不 能 用 它 作 為 公 共 的 回 調(diào) 函 數(shù) 注 冊 。 TWinControl.CreateWnd 調(diào) 用 CreateParams 獲得要注冊的窗口類的資料,然后使用 Controls.pas 中的靜態(tài) 函數(shù) InitWndProc 作為窗口回調(diào)函數(shù)進(jìn)行窗口類的注冊。 InitWndPro
27、c 的參數(shù) 符合 Windows 回調(diào)函數(shù)的標(biāo)準(zhǔn)。 InitWndProc 第一次被回調(diào)時(shí)就把新建窗口 (注 意不是窗口類 的回調(diào)函數(shù)替換為對象的 TWinControl.FObjectInstance (這是 一種 Windows subclassing 技術(shù) ,并且使用 SetProp 把對象的地址保存在新 建窗口的屬性表中,供 Delphi 的輔助函數(shù)讀取 (比如 Controls.pas 中的 FindControl 函數(shù) ??傊? TWinControl.FObjectInstance 最終是被注冊為窗口回調(diào)函數(shù)了。這樣,如果 TWinControl 對象所創(chuàng)建的窗口收到消息后 (形
28、象的說法 ,會(huì)被 Windows 回調(diào) TWinControl.FObjectInstance,而 FObjectInstance 會(huì)呼叫該 對象的 TWinControl.MainWndProc 函數(shù)。 就這樣 VCL 完成了對象的消息處理過 程與 Windows 要求的回調(diào)函數(shù)格式差異的轉(zhuǎn)換。 注意, 在轉(zhuǎn)換過程中, Windows 回調(diào)時(shí)傳遞進(jìn)來的第一個(gè)參數(shù) HWND 被拋棄了。因此 Delphi 的組件必須使用 TWinControl.Handle (或 protected 中的 WindowHandle 來得到這個(gè)參數(shù)。 Windows 回調(diào)函數(shù)需要傳回的返回值也被替換為 TMess
29、age 結(jié)構(gòu)中的最后一個(gè) 字段 Result。為 了 使 大 家 更 清 楚 窗 口 被 回 調(diào) 的 過 程 , 我 把 從 DispatchMessage 開 始 到 TWinControl.MainWndProc 被 調(diào) 用 的 匯 編 代 碼 (你 可 以 把 從 FObjectInstance.Code 開始至最后一行的代碼看成是一個(gè)標(biāo)準(zhǔn)的窗口回調(diào)函 數(shù) :DispatchMessage(&Msg / Application.Run 呼叫 DispatchMessage 通知/ Windows 準(zhǔn)備回調(diào)Windows 準(zhǔn)備回調(diào) TWinControl.FObjectInstance 前
30、在堆棧中設(shè)置參數(shù): push LPARAMpush WPARAMpush UINTpush HWNDpush (eip.Next ; 把 Windows 回調(diào)前下一條語句 的地址; 保存在堆棧中jmp FObjectInstance.Code ; 調(diào) 用 TWinControl.FObjectInstanceFObjectInstance.Code 只有一句 call 指令 :call ObjectInstance.offsetpush eip.Nextjmp InstanceBlock.Code ; 調(diào)用 InstanceBlock.CodeInstanceBlock.Code:pop ec
31、x ; 將 eip.Next 的值存入 ecx, 用于; 取 MainWndProc 和 Self jmp StdWndProc ; 跳轉(zhuǎn)至 StdWndProcStdWndProc 的匯編代碼 :function StdWndProc(Window: HWND; Message, WParam: Longint;LParam: Longint: Longint; stdcall; assembler;asmpush ebpmov ebp, espXOR EAX,EAXxor eax, eaxPUSH EAXpush eax ; 設(shè)置 Message.Result := 0 PUSH LPar
32、am ; 為什么 Borland 不從上面的堆 棧中直接push dword ptr ebp+$14 ; 獲取這些參數(shù)而要重新 push 一 遍?PUSH WParam ; 因?yàn)?TMessage 的 Result 是 push dword ptr ebp+$10 ; 記錄的最后一個(gè)字段,而回調(diào)函 數(shù)的 HWNDPUSH Message ; 是第一個(gè)參數(shù),沒有辦法兼容。 push dword ptr ebp+$0cMOV EDX,ESPmov edx, esp ; 設(shè)置 Message 在堆棧中的地址 為; MainWndProc 的參數(shù)MOV EAX,ECX.Longint4mov eax,
33、 ecx+$04 ; 設(shè)置 Self 為 MainWndProc 的 隱含參數(shù)CALL ECX.Pointercall dword ptr ecx : 呼 叫 TWinControl.MainWndProc(Self,; MessageADD ESP,12add esp, $0cPOP EAXpop eaxend;pop ebpret $0010mov eax, eax看不懂上面的匯編代碼,不影響對下文討論的理解。= = 補(bǔ)充知識:TWndMethod 概述= =寫這段基礎(chǔ)知識是因?yàn)槲以陂喿x MakeObjectInstance(MainWndProc 這句時(shí)不 知道究竟傳遞了什么東西給 Ma
34、keObjectInstance。弄清楚了 TWndMethod 類型 的含義還可以理解后面 VCL 消息系統(tǒng)中的一個(gè)小技巧。TWndMethod = procedure(var Message: TMessage of object;這句類型聲明的意 思 是:TWndMethod 是 一種過程 類型, 它指向一個(gè) 接收 TMessage 類型參數(shù)的過程,但它不是一般的靜態(tài)過程,它是對象相關(guān) (object related 的。 TWndMethod 在內(nèi)存中存儲為一個(gè)指向過程的指針和一個(gè)對象的指 針,所以占用 8個(gè)字節(jié)。 TWndMethod 類型的變量必須使用已實(shí)例化的對象來賦 值。舉個(gè)例
35、子:varSomeMethod: TWndMethod;beginSomeMethod := Form1.MainWndProc; / 正確。這時(shí) SomeMethod 包含 /MainWndProc和 Form1 的指針, /可以用 SomeMethod(Msg來執(zhí)行。 SomeMethod := TForm.MainWndProc; / 錯(cuò)誤!不能用類引用。end;如果把 TWndMethod變量賦值給虛方法會(huì)怎樣?舉例:varSomeMethod: TWndMethod;beginSomeMethod := Form1.WndProc; / TForm.WndProc 是虛方法end;這
36、時(shí), 編譯器實(shí)現(xiàn)為 SomeMethod 指向 Form1 對象虛方法表中的 WndProc 過程 的地址和 Form1 對象的地址。也就是說編譯器正確地處理了虛方法的賦值。調(diào) 用 SomeMethod(Message 就等于調(diào)用 Form1.WndProc(Message。在可能被賦值的情況下,對象方法最好不要設(shè)計(jì)為有返回值的函數(shù) (function, 而要設(shè)計(jì)為過程 (procedure。原因很簡單,把一個(gè)有返回值的對象方法賦值給 TWndMethod 變量,會(huì)造成編譯時(shí)的二義性。= = VCL 的消息處理從 TWinControl.MainWndProc 開始= =通 過 對 Appli
37、cation.Run 、 TWinControl.Create 、 TWinControl.Handle 和 TWinControl.CreateWnd 的討論,我們現(xiàn)在可以把焦點(diǎn)轉(zhuǎn)向 VCL 內(nèi)部的消息處 理過程。 VCL 控件的消息源頭就是 TWinControl.MainWndProc 函數(shù)。 (如果不能 理解這一點(diǎn),請重新閱讀上面的討論。 讓我們先看一下 MainWndProc 函數(shù)的代碼 (異常處理的語句被我刪除 :procedure TWinControl.MainWndProc(var Message: TMessage;beginWindowProc(Message;end;T
38、WinControl.MainWndProc 以 引 用 (也 就 是 隱 含 傳 地 址 的 方 式 接 受 一 個(gè) TMessage 類型的參數(shù), TMessage 的定義如下 (其中的 WParam 、 LParam 、 Result 各有 HiWord 和 LoWord 的聯(lián)合字段,被我刪除了,免得代碼太長 :TMessage = packed recordMsg: Cardinal;WParam: Longint;LParam: Longint;Result: Longint;end;TMessage 中并沒有窗口句柄,因?yàn)檫@個(gè)句柄已經(jīng)在窗口創(chuàng)建之后保存在 TWinControl.H
39、andle 之中。 TMessage.Msg 是消息的 ID 號,這個(gè)消息可以是 Windows 標(biāo)準(zhǔn)消息、 用戶定義的消息或 VCL 定義的 Control 消息等。 WParam 和 LParam 與標(biāo)準(zhǔn) Windows 回調(diào)函數(shù)中 wParam 和 lParam 的意義相同, Result 相當(dāng)于標(biāo)準(zhǔn) Windows 回調(diào)函數(shù)的返回值。注意 MainWndProc 不是虛函數(shù), 所以它不能被 TWinControl 的繼承類重載。 (思 考:為什么 Borland 不將 MainWndProc 設(shè)計(jì)為虛函數(shù)呢? MainWndProc 中建立兩層異常處理, 用于釋放消息處理過程中發(fā)生異常
40、時(shí)的資源 泄 漏 , 并 調(diào) 用 默 認(rèn) 的 異 常 處 理 過 程 。 被 異 常 處 理 包 圍 著 的 是 WindowProc(Message。 WindowProc 是 TControl(而不是 TWinControl 的一個(gè) 屬性 (property:property WindowProc: TWndMethod read FWindowProc write FWindowProc;WindowProc 的類型是 TWndMethod, 所以它是一個(gè)對象相關(guān)的消息處理函數(shù)指針 (請參考前面 TWndMethod 的介紹 。 在 TControl.Create 中 FWindowPr
41、oc 被賦 值為 WndProc。WndProc 是 TControl 的一個(gè)函數(shù),參數(shù)與 TWinControl.MainWndProc 相同: procedure TControl.WndProc(var Message: TMessage; virtual;原來 MainWndProc 只是個(gè)代理函數(shù),最終處理消息的是 TControl.WndProc 函 數(shù)。那么 Borland 為什么要用一個(gè) FWindowProc 來存儲這個(gè) WndProc 函數(shù),而不 直接調(diào)用 WndProc 呢?我猜想可能是基于效率的考慮。 還記得上面 TWndMethod 的討論嗎?一個(gè) TWndMetho
42、d 變量可以被賦值為一個(gè)虛函數(shù), 編譯器對此操作的 實(shí)現(xiàn)是通過對象指針訪問到了對象的虛函數(shù)表, 并把虛函數(shù)表項(xiàng)中的函數(shù)地址傳 回。由于 WndProc 是一個(gè)調(diào)用頻率非常高的函數(shù) (可能要用“百次 /秒”或“千 次 /秒”來計(jì)算 ,所以如果每次調(diào)用 WndProc 都要訪問虛函數(shù)表將會(huì)浪費(fèi)大量 時(shí)間, 因此 在 TControl 的構(gòu) 造函 數(shù)中 就 把 WndProc 的真 正 地址存 儲在 WindowProc 中,以后調(diào)用 WindowProc 將就轉(zhuǎn)換為靜態(tài)函數(shù)的調(diào)用,以加快處 理速度。= = TWinControl.WndProc= =轉(zhuǎn)了層層彎,到現(xiàn)在我們才剛進(jìn)入 VCL 消息系統(tǒng)
43、處理開始的地方:WndProc 函數(shù)。 如前所述, TWinControl.MainWndProc 接收到消息后并沒有處理消息, 而是 把消息傳遞給 WindowProc 處理。由于 WindowProc 總是指向當(dāng)前對象的 WndProc 函數(shù)的地址, 我們可以簡單地認(rèn)為 WndProc 函數(shù)是 VCL 中第一個(gè)處理 消息的函數(shù),調(diào)用 WindowProc 只是效率問題。WndProc 函數(shù)是個(gè)虛函數(shù),在 TControl 中開始定義,在 TWinControl 中被重 載。 Borland 將 WndProc 設(shè)計(jì)為虛函數(shù)就是為了各繼承類能夠接管消息處理, 并把未處理的消息或加工過的消息傳
44、遞到上一層類中處理。這里將消息處理的傳遞過程和對象的構(gòu)造函數(shù)稍加對比:對象的構(gòu)造函數(shù)通常會(huì)在第一行代碼中使用 inherited 語句調(diào)用父類的構(gòu)造函 數(shù)以初始化父類定義的成員變量, 父類也會(huì)在構(gòu)造函數(shù)開頭調(diào)用祖父類的構(gòu)造函 數(shù),如此遞歸,因此一個(gè) TWinControl 對象的創(chuàng)建過程是 TComponent.Create - TControl.Create - TWinControl.Create。而消息處理函數(shù) WndProc 則是先處理自己想要的消息,然后看情況是否要遞交 到父類的 WndProc 中處理。所以消息的處理過程是 TWinControl.WndProc - TContro
45、l.WndProc 。因此,如果要分析消息的處理過程,應(yīng)該從子類的 WndProc 過程開始,然后才 是父類的 WndProc 過程。由于 TWinControl 是第一個(gè)支持窗口創(chuàng)建的類,所 以它的 WndProc 是很重要的,它實(shí)現(xiàn)了最基本的 VCL 消息處理。TWinControl.WndProc 主要是預(yù)處理一些鍵盤、 鼠標(biāo)、 窗口焦點(diǎn)消息, 對于不必 響 應(yīng) 的 消 息 , TWinControl.WndProc 直 接 返 回 , 否 則 把 消 息 傳 遞 至 TControl.WndProc 處理。從 TWinControl.WndProc 摘抄一段看看:WM_KEYFIRST
46、.WM_KEYLAST:if Dragging then Exit; / 注意:使用 Exit 直接返回這段代碼的意思是:如果當(dāng)前組件正處于拖放狀態(tài),則丟棄所有鍵盤消息。再看一段:WM_MOUSEFIRST.WM_MOUSELAST:if IsControlMouseMsg(TWMMouse(Message thenbegin Check HandleAllocated because IsControlMouseMsg might have freed thewindow if user code executed something like Parent := nil. if (Mess
47、age.Result = 0 and HandleAllocated thenDefWindowProc(Handle, Message.Msg, Message.wParam,Message.lParam;/ DefWindowProc 是 Win32 API 中缺省處理消息的函數(shù)Exit;end;這里的 IsControlMouseMsg 很關(guān)鍵。 讓我們回憶一下:TControl 類的對象并沒 有創(chuàng)建 Windows 窗口,它是怎樣接收到鼠標(biāo)和重繪等消息的呢?原來這些消息 就是由它的 Parent 窗口發(fā)送的。在上面的代碼中, TWinControl.IsControlMouseMsg
48、判斷鼠標(biāo)地址是否落在 TControl 類 控 件 上 , 如 果 不 是 就 返 回 否 值 。 TWinControl 再 調(diào) 用 TControl.WndProc , TControl.WndProc 又調(diào)用了 TObject.Dispatch 方法,這 是后話。如果當(dāng)前鼠標(biāo)地址落在窗口上的 TControl 類控件上, 則根據(jù) TControl 對象的 相對位置重新生成了鼠標(biāo)消息, 再調(diào)用 TControl.Perform 方法把加工過的鼠標(biāo) 消息直接發(fā)到 TControl.WndProc 處理。 TControl.Perform 方法以后再談。如果 TWinControl 的繼承類重
49、載 WndProc 處鼠標(biāo)消息,但不使用 inherited 把消息傳遞給父類處理,則會(huì)使從 TControl 繼承下來的對象不能收到鼠標(biāo)消 息。現(xiàn)在我們來做個(gè)試驗(yàn),下面 Form1 上的 TSpeedButton 等非窗口控件不會(huì) 發(fā)生 OnClick 等鼠標(biāo)事件。procedure TForm1.WndProc(var Message: TMessage; override;begincase Message.Msg ofWM_MOUSEFIRST.WM_MOUSELAST:beginDefWindowProc(Handle, Message.Msg, Message.WParam, Me
50、ssage.LParam;Exit; / 直接退出end;elseinherited;end;end;TWinControl.WndProc 的最后一行代碼是:inherited WndProc(Message;也就是調(diào)用 TControl.WndProc。 讓我們來看看 TControl.WndProc 做了些什么。 = TControl.WndProc= =TControl.WndProc 主要實(shí)現(xiàn)的操作是:響應(yīng)與 Form Designer 的交互 (在設(shè)計(jì)期間 在控件不支持雙擊的情況下把鼠標(biāo)雙擊事件轉(zhuǎn)換成單擊判斷鼠標(biāo)移動(dòng)時(shí)是否需要顯示提示窗口 (HintWindow判斷控件是否設(shè)置為
51、AutoDrag,如果是則執(zhí)行控件的拖放處理調(diào)用 TControl.MouseWheelHandler 實(shí)現(xiàn)鼠標(biāo)滾輪消息使用 TObject.Dispatch 調(diào)用 DMT 消息處理方法TControl.WndProc 相對比較簡單,在此只隨便談?wù)劦诙l。你是否有過這樣的 使用經(jīng)驗(yàn):在你快速雙擊某個(gè)軟件的 Button 時(shí),只形成一次 Click 事件。所 以如果你需要設(shè)計(jì)一個(gè)不管用戶用多快的速度點(diǎn)擊,都能生成同樣點(diǎn)擊次數(shù) Click 事件的按鈕時(shí),就需要參考 TControl.WndProc 處理鼠標(biāo)消息的過程了。TControl.WndProc 最后一行代碼是 Dispatch(Messa
52、ge,也就是說如果某個(gè)消 息沒有被 TControl 以后的任何類處理,消息會(huì)被 Dispatch 處理。TObject.Dispatch 是 Delphi VCL 消息體系中非常關(guān)鍵的方法。= = TObject.Dispatch= =TObject.Dispatch 是個(gè)虛函數(shù),它的聲明如下:procedure TObject.Dispatch(var Message; virtual;請注意它的參數(shù)雖然與 MainWndProc 和 WndProc 的參數(shù)相似, 但它沒有規(guī)定參 數(shù)的類型。這就是說, Dispatch 可以接受任何形式的參數(shù)。Delphi 的文檔指出:Message 參數(shù)
53、的前 2 個(gè)字節(jié)是 Message 的 ID(下文簡稱 為 MsgID,通過 MsgID 搜索對象的消息處理方法。這段話并沒有為我們理解 Dispatch 方法提供更多的幫助, 看來我們必須通過閱 讀源代碼來分析這個(gè)函數(shù)的運(yùn)作過程。TObject.Dispatch 雖然是個(gè)虛方法,但卻沒有被 TPersistent、 TComponent 、 TControl 、 TWinControl 、 TForm 等 后 續(xù) 類 重 載 ( TCommonDialog 調(diào) 用 了 TObject.Dispatch , 但 對 于 整 個(gè) VCL 消 息 系 統(tǒng) 并 不 重 要 , 并 且 只 由 TControl.WndProc 調(diào)用過。 所以可以簡單地認(rèn)為如果消息沒有在 WndProc 中被處理,則被 TObject.Dispatch 處理。我們很容易查覺到一個(gè)很重要的問題:MsgID 是 2 個(gè)字節(jié), 而 TMessage.Msg 是 4 個(gè)字節(jié),如果 TContr
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 主播上崗考試題及答案
- 靜物寫生素描考試題及答案
- 宣傳接待崗面試題及答案
- 廣東省廣州市番禺育才2022-2023學(xué)年八年級下學(xué)期期中考試物理試題(含答案)
- 抗體檢驗(yàn)知識培訓(xùn)課件
- 創(chuàng)新型醫(yī)療器械研發(fā)協(xié)議
- 關(guān)于職場軟技能培養(yǎng)的建議與反思
- 小學(xué)生科學(xué)知識讀物征文
- 員工技能培訓(xùn)安排表
- 全球教育資源分布及質(zhì)量評估表
- 16J914-1 公用建筑衛(wèi)生間
- 果汁加工工藝
- 外協(xié)加工流程圖
- 瀝青混凝土路面施工質(zhì)量通病防治措施
- 高中地理 選擇性必修二 紐約的發(fā)展 紐約的輻射功能 城市的輻射功能 課件(第2課時(shí))
- 抽油井示功圖分析以及應(yīng)用
- 新藥發(fā)明簡史
- 高分子物理化學(xué)全套課件
- 【學(xué)海導(dǎo)航】2013屆高三物理一輪復(fù)習(xí) 第11章 第3節(jié) 電磁振蕩與電磁波 電磁波譜課件 新人教版
- 電工plc培訓(xùn)-技工技能類
- 電力系統(tǒng)碳排放流的計(jì)算方法初探_周天睿
評論
0/150
提交評論