跳轉到內容

Windows 程式設計/訊息迴圈架構

來自 Wikibooks,開放書籍,開放世界

用 C 語言編寫 Windows 程式與大多數人習慣的有所不同。首先,沒有 main() 函式,而是使用 _tWinMain() 函式作為程式的入口點。_tWinMain() 在 tchar.h 中定義為宏,如下所示

#ifdef _UNICODE
#define _tWinMain wWinMain
#else 
#define _tWinMain WinMain
#endif

這意味著 Windows 函式可以輕鬆地用 Unicode 或 ASCII 編寫。除了函式名不同外,_tWinMain 還有不同於標準 main 函式的引數

int WinMain(HINSTANCE hThisInstance, 
              HINSTANCE hPrevInstance, 
              LPSTR lpszArgument,  // LPWSTR for wWinMain
              int iCmdShow);

HINSTANCE 物件是程式例項的引用。hThisInstance 是當前程式的引用,而 hPrevInstance 是之前執行的相同程式的引用。但是,在 Windows NT 中,hPrevInstance 資料物件始終為 NULL。這意味著我們無法使用 hPrevInstance 值來確定系統中是否運行了其他相同程式的副本。檢查系統中是否運行了其他相同程式副本的方法有很多,我們將在稍後討論這些方法。

lpszArgument 本質上包含 argc 和 argv 變數過去顯示的所有資訊,但命令列引數不會被拆分成向量。這意味著,如果你想從命令列中排序單個引數,則需要自己拆分它們,或者呼叫可用函式中的某個函式來為你拆分它們。我們將在稍後討論這些函式。

最後一個引數 "int iCmdShow" 是一個 int 型別(整數)的資料項,用於確定是否立即顯示圖形視窗,或者程式是否應最小化執行。

WinMain 有許多不同的任務

  1. 註冊程式要使用的視窗類
  2. 建立程式使用的任何視窗
  3. 執行訊息迴圈

我們將更詳細地解釋每個任務。

註冊視窗類

[編輯 | 編輯原始碼]

你在螢幕上看到的每個圖形細節都被稱為“視窗”。每個帶有邊框的程式、每個按鈕、每個文字框都被稱為視窗。實際上,所有這些不同的物件都是以相同的方式建立的。這意味著為了從相同方法中獲得像文字框和捲軸這樣不同的物件,必須有大量的選項和自定義內容。每個視窗都有一個關聯的“視窗類”,需要在系統中註冊,以指定視窗的不同屬性。這可以透過以下 2 個步驟完成

  1. 填寫 WNDCLASS 資料物件的欄位
  2. 將 WNDCLASS 物件傳遞給 RegisterClass 函式。

此外,還有一種“擴充套件”版本,可以執行類似的操作

  1. 填寫 WNDCLASSEX 物件的欄位
  2. 將 WNDCLASSEX 物件傳遞給 RegisterClassEx 函式。

這兩種方法都可以用來註冊類,但 -Ex 版本有一些額外的選項。

註冊類後,你可以丟棄 WNDCLASS 結構,因為你不再需要它。

建立視窗

[編輯 | 編輯原始碼]

可以使用 CreateWindow 或 CreateWindowEx 函式建立視窗。兩者執行相同的通用任務,但 -Ex 版本也有更多選項。你將一些具體內容傳遞給 CreateWindow 函式,例如視窗大小、視窗位置(X 和 Y 座標)、視窗標題等。CreateWindow 將返回一個 HWND 資料物件,即新建立視窗的控制代碼。接下來,大多數程式會將此控制代碼傳遞給 ShowWindow 函式,以使窗口出現在螢幕上。CreateWindow() 建立視窗的方式如下

hwnd=CreateWindowEx(
     WS_EX_CLIENTEDGE,
     g_szClassName,
     "The title of my window",
     WS_OVERLAPPEDWINDOW,
     CW_USEDEFAULT,CW_USEDEFAULT,240,120,
     NULL,NULL,hInstance,NULL);

第一個引數 WS_EX_CLIENTEDGE 是擴充套件視窗樣式。接下來是類名 g_szClassName,它告訴系統要建立哪種視窗。由於我們要從剛剛註冊的類中建立視窗,因此我們使用該類的名稱。之後,我們指定視窗名稱或標題,即在標題或視窗上的標題欄中顯示的文字。作為引數的 WS_OVERLAPPEDWINDOW 是視窗樣式引數。有很多這樣的引數,你應該查閱它們並進行試驗,以找出它們的作用。接下來的四個引數(CW_USEDEFAULT,CW_USEDEFAULT,240,120)是視窗左上角的 X 和 Y 座標,以及視窗的寬度和高度。X 和 Y 座標被設定為 CW_USEDEFAULT,以讓視窗選擇在螢幕上的哪個位置放置視窗。接下來,(NULL,NULL,hInstance,NULL),我們有父視窗控制代碼、選單控制代碼、應用程式例項控制代碼和指向視窗建立資料的指標控制代碼。在 Windows 中,螢幕上的視窗按父視窗和子視窗的層次結構排列。當看到視窗上的按鈕時,按鈕是子視窗,它包含在其父視窗中。在本例中,父視窗控制代碼為 NULL,因為我們沒有父視窗,這是我們的主視窗或頂級視窗。現在選單為 NULL,因為我們還沒有選單。例項控制代碼設定為作為 WinMain() 的第一個引數傳遞的值。可以用來向正在建立的視窗傳送額外資料的建立資料為 NULL。

訊息迴圈

[編輯 | 編輯原始碼]

視窗建立後,視窗將透過 訊息 與系統中的其他部分進行互動。系統向視窗傳送訊息,視窗也向系統傳送訊息。實際上,大多數程式只做一件事,就是讀取訊息並響應訊息!

訊息以 MSG 資料型別的形式出現。此資料物件被傳遞給 GetMessage() 函式,該函式從訊息佇列中讀取訊息,或者等待系統傳送的新訊息。接下來,訊息被髮送到 TranslateMessage 函式,該函式處理一些簡單的任務,例如轉換為 Unicode 或不轉換為 Unicode。最後,使用 DispatchMessage 函式將訊息傳送到視窗進行處理。

以下是一個示例

MSG msg;
BOOL bRet;
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{ 
   if (bRet == -1)
   {
       // handle the error and possibly exit
   }
   else
   {
       TranslateMessage(&msg); 
       DispatchMessage(&msg); 
   }
}

return msg.wParam;

最後一行將在稍後解釋。

視窗過程

[編輯 | 編輯原始碼]

視窗過程可以命名為任何你想要的名稱,但其通用原型如下所示

LRESULT CALLBACK WinProc (HWND hwnd, 
                          UINT msg, 
                          WPARAM wParam, 
                          LPARAM lParam);

LRESULT 資料型別是一個通用的 32 位資料物件,可以將其型別轉換為包含任何 32 位值(包括指標)。hwnd 引數是視窗本身的控制代碼。msg 資料值包含來自作業系統的當前訊息,而 WPARAM 和 LPARAM 值包含該訊息的引數。例如,如果按下鍵盤上的某個按鈕,msg 欄位將包含訊息 WM_KEYDOWN,而 WPARAM 欄位將包含實際按下的字母(例如 'A'),而 LPARAM 欄位將包含有關 CTRL、ALT 或 SHIFT 按鈕是否按下以及是否已觸發型別化重複功能的資訊。已定義了一些宏,在將 WPARAM 和 LPARAM 分離成不同大小的塊時非常有用

LOWORD(x)
返回 32 位引數的低 16 位
HIWORD(x)
返回 32 位引數的高 16 位
LOBYTE(x)
返回 16 位引數的低 8 位
HIBYTE(x)
返回 16 位引數的高 8 位

例如,要訪問 wParam 欄位中的第二個位元組,我們將使用如下所示的宏

HIBYTE(LOWORD(wParam));

由於視窗過程只有兩個可用引數,因此這些引數通常包含資料。這些宏在將這些資訊分離成不同的欄位時非常有用。

以下是一個一般視窗過程的示例,我們將進行解釋

LRESULT CALLBACK MyWinProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch (msg)       
   {
       case WM_DESTROY:
           PostQuitMessage(0);
           break;
       default:           
           return DefWindowProc (hwnd, msg, wParam, lParam);
   }
   return 0;
}

大多數視窗過程只包含一個簡單的迴圈,該迴圈搜尋來自系統的特定訊息,然後對它們進行操作。在本例中,我們只在查詢 WM_DESTROY 訊息,這是核心在視窗需要關閉時傳送給視窗的訊息。響應 WM_DESTROY 訊息,視窗呼叫 PostQuitMessage 函式,該函式將 WM_QUIT 訊息(定義為 0)放入訊息佇列。當訊息迴圈(如上所述)接收到 WM_QUIT 訊息時,它將從迴圈中退出,並返回來自 PostQuitMessage 函式的值。

任何未被迴圈處理的訊息都應(必須)傳遞給 DefWindowProc 函式。DefWindowProc 將根據收到的訊息執行一些預設操作,但它不會做任何有趣的事情。如果你希望你的程式執行一些操作,你需要自己處理這些訊息。

我們將在後面討論其他一些訊息

WM_CREATE
您的視窗只在第一次建立時收到此訊息。使用此訊息執行需要在開始時處理的任務,例如初始化變數、分配記憶體或建立子視窗(按鈕和文字框)。
WM_PAINT
此訊息指示程式需要重新繪製自身。使用圖形函式重新繪製視窗上應該顯示的內容。如果你沒有繪製任何內容,那麼視窗將是無聊的白色(或灰色)背景,或者如果背景沒有被擦除,將保留顯示在上面的任何影像(這看起來不穩定)。
WM_COMMAND
這是一個通用訊息,指示使用者在您的視窗上做了什麼。使用者要麼單擊了一個按鈕,要麼選擇了選單項,要麼按下了特殊的“加速鍵”序列。WPARAM 和 LPARAM 欄位將包含有關發生的事情的一些描述,以便您可以找到一種對它做出反應的方式。如果您沒有處理 WM_COMMAND 訊息,使用者將無法單擊任何按鈕或選擇任何選單項,這將非常令人沮喪。
WM_CLOSE
使用者決定關閉視窗,因此核心傳送 WM_CLOSE 訊息。這是儲存視窗的最後機會 - 如果你不想完全關閉它,你應該處理 WM_CLOSE 訊息並確保它不會銷燬視窗。如果 WM_CLOSE 訊息傳遞給 DefWindowProc,那麼視窗將接下來接收 WM_DESTROY 訊息。
WM_DESTROY
WM_DESTROY 指示給定視窗從螢幕中移除並將從記憶體中解除安裝。通常,您的程式可以透過呼叫 PostQuitMessage() 將 WM_QUIT 訊息釋出到程式以退出程式。

這些是一些最基本的訊息,我們將根據需要討論其他訊息。

- 提示 SendMessage(hwnd,MACRO,NULL,NULL) 可用於傳送使用者定義的訊息。

下一章

[編輯 | 編輯原始碼]
華夏公益教科書