《Inside VCL(深入核心——VCL架構剖析)》.(李維) 一
一、回到從前:
1.1、多工作業系統是如何設計和實現的?1.1.1、系統——多個應用程式
方案1:(系統不斷讀取應用程式狀態)
系統通過大型迴圈(Loop)不斷堅持麼一個惡用用程式是否觸發了特定的事件。
方案2:(事件驅動模型——事件/訊息處理模型)
執行環境將事件轉換成代表事件的訊息,然後傳送給對應的應用程式。
//訊息大概格式
TMyMessage = packed record //用來儲存事件資訊
Message : Longint;
wParam : Longint;
lParam : Longint;
time : Longint;
pt : TPoint;
end;
1.2、事件/訊息模型還需要解決的問題:1、應用程式可能擁有多個視窗,如何把觸發的訊息分派給正確地視窗去處理?
2、把訊息分派給應用程式或者視窗是如何辦到的?
3、當分派了訊息給視窗之後,視窗要如何處理此訊息?
1.2.1、視窗運作模型:(問題1)
執行環境中可能有多個應用程式,一個應用程式可能建立多個視窗。如何保證執行環境的事件能分配到正確應用程式的正確視窗?
因此,我們可以在MyMessage中再加入一個代表視窗的識別值來分辨應用程式的不同視窗:
TMyMessage = packed record
hwnd : HWND;
Message : Longint;
wParam : Longint;
lParam : Longint;
time : Longint;
pt : TPoint;
end;
hwnd: HWND;//代表視窗的標識。訊息中有唯一代表窗體的hWnd,通過hwnd可以準確的將訊息傳送到對應的視窗。
2、快取記憶體(caching)執行環境為每個應用程式建立一個訊息佇列(Message Queue)。當事件發生時執行環境酒吧代表他的訊息分派到此訊息佇列,等待應用程式從中取出並且處理。
如此,問題1解決。
1.2.2、執行系統、事件、訊息和觸發應用程式程式碼
1.2.2.1、執行環境需要哪些資訊才能正確地為應用程式建立視窗呢?
視窗的位置和大小;
視窗的格式、使用的顏色以及使用的游標種類;
視窗使用的選單以及其他資源;
當視窗發生事件時,能夠處理視窗訊息的函式地址。
執行環境提供了一個標準視窗的類別資料結構,在這個標準資料結構中即可定義上述資訊的欄位,應用程式只要在每個欄位中填入正確地資訊已表達想要建立的視窗格式即可。
MyWindowClassInfo = packed record
style: UINT; //代表視窗的格式
iWidth: Integer;
iHeight: Integer;
lpfnWndProc: Pointer; //處理視窗訊息的函式的地址
hIcon: HICON;
hCursor: HCURSOR;
hbrBackground: HBRUSH;
lpszMenuName: PAnsiChar;
lpszClassName: PAnsiChar; //視窗的類名稱
hIconSm: HICON;
end;
應用程式為每個窗體向系統提供MyWindowsClassInfo資料結構,再分別向執行環境註冊每一個窗體的MyWindowsClassInfo資訊。當建立特定視窗種類時,只需要提供要建立視窗的類名稱,執行環境再根據視窗的類名稱找到相應的MyWindowsClassInfo資料結構,最後根據MyWindowsClassInfo中的資訊來建立視窗。
解決了執行環境如何建立視窗的問題後,那麼,窗體如何來處理執行環境發生過來的事件訊息?
讓執行環境呼叫視窗提供的函式並且傳遞訊息給此函式來處理,在上面部分MyWindowClassInfo的lpfnWndProc欄位就指向了處理該視窗訊息的函式(回撥函式)。執行環境通過呼叫該函式,將訊息分派給指定的窗體。
執行環境需定義回撥函式的原型:
function WindowProced(Window: HWnd; AMessage: UINT; WParam: WPARAM; LParam: LPARAM):LRESULT; stdcall; export;
在上面的回撥函式原型中引數Window、WPrarm和LPrarm欄位,代表發生事件的視窗ID以及視窗訊息的輔助資訊等。而引數AMessage則代表事件的視窗訊息值。因此當英語程式遵照回撥函式的原型提供了適當的處理視窗訊息的函式並且指定給MyWindowClassInfo中的lpfnWndProc欄位,那麼應用程式就可以等待這個回撥函式在視窗事件發生後自動由執行環境呼叫了。(至此,問題2解決)
最後,當執行環境呼叫了回撥函式後,其中的AMessage就代表了發生事件的ID,因此在回撥函式中就可以通過判斷AMessage的數值來得知發生的時間,再格恩局不同的時間使用相應的程式程式碼來處理:
case AMessage of
WM_PAINT://重畫窗體的訊息
...
WM_DESRROY://窗體消滅的訊息
...
end;
由於應用程式要判斷回撥函式中發生的訊息ID值,因此執行環境當然必須為觸發時間定義代表訊息ID及每個訊息的意義了:
unti Messages;
...
const
WM_NULL = $0000;
WM_Create = $0001;
WM_DESTROY = $0002;
WM_MOVE = $0003;
...
WM_PAINT = $000F;
...
應用程式只需要uses這個訊息單元就可以根據其中的訊息ID值來判斷AMessage代表的事件了。(至此,問題3解決)現在我們幾乎已經完整地定義了執行環境如何使用 事件/訊息/回撥函式 的機制在多工環境中提供應用程式處理事件的機制。首先,應用程式經由填入MyWindowClassInfo資料結構來註冊視窗資訊,之後應用程式在呼叫建立視窗並且提供視窗回撥函式。
當應用程式欲建立視窗時只要提供視窗類名稱,執行環境便會在所有已經註冊的視窗類中搜尋MyWindowClassInfo中lpszClassName欄位值與提供的視窗類名稱一致的MyWindowClassInfo資料結構。找到之後根據其中的視窗特性值來建立按視窗。
當執行環境產生訊息後,會根據視窗的ID值,即MyMessage中的hwnd欄位,找到此視窗的MyWindowClassInfo資料結構,再呼叫其中的回撥函式欄位lpfnWndProc。如此,執行環境便可以正確呼叫到應用程式提供的視窗訊息處理函數了。