Windows 是典型的「訊息驅動」作業系統。所有使用者輸入(鍵盤、滑鼠)、系統事件(計時器、電源狀態變更)、應用程式間通訊,最終都轉化為一個個訊息投遞到目標視窗。深入理解訊息結構、派發流程與訊息迴圈,是撰寫高效、穩定 Windows 程式的基石。
一個訊息由三個核心欄位組成:
UINT message:訊息識別碼(如 WM_PAINT、WM_LBUTTONDOWN)。WPARAM wParam:32 位元參數,通常承載鍵盤鍵碼、控制項 ID、通知碼高位等。LPARAM lParam:32 位元參數,通常承載滑鼠座標、指標位址、通知碼低位等。開發者亦可自訂訊息(建議從 WM_USER + 100 或 RegisterWindowMessage 取得唯一值),用於執行緒/進程間通訊。
每個訊息必須指定目標視窗控制代碼(HWND)。系統依 HWND 找到對應的視窗程序(WndProc),呼叫該函式處理訊息。同一視窗類別的所有實例共用同一 WndProc,但透過 HWND 區分具體實例。
若應用程式在 WndProc 中未處理某訊息,應呼叫 DefWindowProc 交給系統預設視窗程序處理。這確保視窗仍具備標準行為(拖曳、最小化、繪製邊框等),開發者只需關注業務邏輯相關訊息。
HWND 是系統內核中視窗物件的唯一識別子。PostMessage/SendMessage 必須提供目標 HWND,系統才能將訊息投遞到正確的訊息佇列。兩個視窗即使共用同一 WndProc,因 HWND 不同,訊息也不會混淆。
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
OnPaint(hWnd); break;
case WM_LBUTTONDOWN:
OnLButtonDown(hWnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); break;
case WM_DESTROY:
PostQuitMessage(0); break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
系統為每個執行緒維護一個訊息佇列(16 位元 Windows 僅有一個全系統佇列)。所有針對該執行緒建立視窗的訊息,皆先放入此佇列。
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); // 處理鍵盤訊息產生 WM_CHAR
DispatchMessage(&msg); // 依 HWND 呼叫對應 WndProc
}
GetMessage:佇列為空時阻塞,釋放 CPU 時間片,實現多工協作。PeekMessage:非阻塞檢查,適合遊戲迴圈或需輪詢的場合。| 特性 | 16 位元 (Win 3.x) | 32/64 位元 (Win95+) |
|---|---|---|
| 訊息佇列 | 單一全系統佇列 | 每執行緒一個佇列 |
| 多工模型 | 協作式:前景程式不讓出控制權,系統卡死 | 搶先式:OS 依優先級調度 CPU,單一程式死循環不影響其他 |
| 訊息派發 | 系統輪詢分派 | 執行緒自行 GetMessage/Dispatch |
PostMessage 回報進度,避免訊息迴圈阻塞導致介面凍結。SendMessageTimeout:跨進程送訊息時設定逾時,防止目標進程掛起造成發送端死等。RegisterWindowMessage:保證系統全域唯一,避免衝突。WM_COPYDATA 可跨進程傳遞結構化資料,配合 SendMessage 同步投遞。Windows 訊息機制雖歷經數十年演進,核心概念——訊息佇列、視窗程序、訊息迴圈——始終未變。熟練掌握這套機制,不僅能寫出回應迅速的 GUI 程式,更能從根本上理解 MFC、WTL、Qt 等框架的底層運作,事半功倍。