GetMessage以及消息循環說明

GetMessage以及消息循環說明

在創建窗口、顯示窗口、更新窗口後,我們需要編寫一個消息循環,不斷地從消息隊列中取出消息,並進行響應。要從消息隊列中取出消息,我們需要調用GetMessage()函數,該函數的原型聲明如下:在創建窗口、顯示窗口、更新窗口後,我們需要編寫一個消息循環,不斷地從消息隊列中取出消息,並進行響應。要從消息隊列中取出消息,我們需要調用GetMessage()函數,該函數的原型聲明如下:

BOOL GetMessage(

          LPMSG lpMsg,              // address of structure with message

          HWND hWnd,                 // handle of window

          UINT wMsgFilterMin,       // first message

          UINT wMsgFilterMax        // last message

);

參數lpMsg指向一個消息(MSG)結構體,GetMessage從線程的消息隊列中取出的消息信息將保存在該結構體對象中。

參數hWnd指定接收屬於哪一個窗口的消息。通常我們將其設置爲NULL,用於接收屬於調用線程的所有窗口的窗口消息。

參數wMsgFilterMin指定要獲取的消息的最小值,通常設置爲0。

參數wMsgFilterMax指定要獲取的消息的最大值。如果wMsgFilterMin和wMsgFilter Max都設置爲0,則接收所有消息。

GetMessage函數接收到除WM_QUIT外的消息均返回非零值。對於WM_QUIT消息,該函數返回零。如果出現了錯誤,該函數返回-1,例如,當參數hWnd是無效的窗口句柄或lpMsg是無效的指針時。

通常消息循環代碼如下:

MSG msg;

while(GetMessage(&msg,NULL,0,0))

{

      TranslateMessage(&msg);

      DispatchMessage(&msg);

}

GetMessage函數只有在接收到WM_QUIT消息時,才返回0。此時while語句判斷的條件爲假,循環退出,程序纔有可能結束運行。在沒有接收到WM_QUIT消息時,Windows應用程序就通過這個while循環來保證程序始終處於運行狀態。

TranslateMessage函數用於將虛擬鍵消息轉換爲字符消息。字符消息被投遞到調用線程的消息隊列中,當下一次調用GetMessage函數時被取出。當我們敲擊鍵盤上的某個字符鍵時,系統將產生WM_KEYDOWN和WM_KEYUP消息。這兩個消息的附加參數(wParam和lParam)包含的是虛擬鍵代碼和掃描碼等信息,而我們在程序中往往需要得到某個字符的ASCII碼,TranslateMessage這個函數就可以將WM_KEYDOWN和WM_ KEYUP消息的組合轉換爲一條WM_CHAR消息(該消息的wParam附加參數包含了字符的ASCII碼),並將轉換後的新消息投遞到調用線程的消息隊列中。注意,TranslateMessage函數並不會修改原有的消息,它只是產生新的消息並投遞到消息隊列中。

DispatchMessage函數分派一個消息到窗口過程,由窗口過程函數對消息進行處理。DispachMessage實際上是將消息回傳給操作系統,由操作系統調用窗口過程函數對消息進行處理(響應)。

(1)操作系統接收到應用程序的窗口消息,將消息投遞到該應用程序的消息隊列中。

(2)應用程序在消息循環中調用GetMessage函數從消息隊列中取出一條一條的消息。取出消息後,應用程序可以對消息進行一些預處理,例如,放棄對某些消息的響應,或者調用TranslateMessage產生新的消息。

(3)應用程序調用DispatchMessage,將消息回傳給操作系統。消息是由MSG結構體對象來表示的,其中就包含了接收消息的窗口的句柄。因此,DispatchMessage函數總能進行正確的傳遞。

(4)系統利用WNDCLASS結構體的lpfnWndProc成員保存的窗口過程函數的指針調用窗口過程,對消息進行處理(即“系統給應用程序發送了消息”)。

以上就是Windows應用程序的消息處理過程。

提示:

(1)從消息隊列中獲取消息還可以調用PeekMessage函數,該函數的原型聲明如下所示:

BOOL PeekMessage(

    LPMSG lpMsg,           // message information

    HWND hWnd,              // handle to window

    UINT wMsgFilterMin// first message

    UINT wMsgFilterMax,  // last message

    UINT wRemoveMsg      // removal options

);

前4個參數和GetMessage函數的4個參數的作用相同。最後1個參數指定消息獲取的方式,如果設爲PM_NOREMOVE,那麼消息將不會從消息隊列中被移除;如果設爲PM_REMOVE,那麼消息將從消息隊列中被移除(與GetMessage函數的行爲一致)。關於PeekMessage函數的更多信息,請參見MSDN。

(2)發送消息可以使用SendMessage和PostMessage函數。SendMessage將消息直接發送給窗口,並調用該窗口的窗口過程進行處理。在窗口過程對消息處理完畢後,該函數才返回(SendMessage發送的消息爲不進隊消息)。PostMessage函數將消息放入與創建窗口的線程相關聯的消息隊列後立即返回。除了這兩個函數外,還有一個PostThreadMessage函數,用於向線程發送消息,對於線程消息,MSG結構體中的hwnd成員爲NULL。

消息循環錯誤分析

   while(GetMessage(&msg,hwnd,0,0))

     {

         TranslateMessage(&msg);

         DispatchMessage(&msg);

     }

注意代碼中以粗體顯示的部分。這段代碼基於這樣一個想法:第1章的程序只有一個窗口,而我們前面說了GetMessage函數的hWnd參數是用於指定接收屬於哪一個窗口的消息,於是不少人就在消息循環中爲GetMessage函數的hWnd參數指定了CreateWindow函數返回的窗口句柄。

讀者可以用上述代碼中的消息循環部分替換1.5節代碼中的消息循環部分,然後運行程序,關閉程序。你會發現你的機器變慢了,同時按下鍵盤上的Ctrl + Alt + Delete鍵,啓動Windows的任務管理器,切換到“進程”選項卡,單擊“CPU”項進行排序,你會發現如圖1.7所示的情況。

從圖1.7中可以看到,WinMain.exe的CPU佔用率接近100,難怪機器“變慢了”。那麼這是什麼原因呢?實際上這個問題的答案在MSDN中就可以找到,並且就在GetMessage函數的說明文檔中。不少初學者在遇到問題時,首先是頭腦一片空白,接着就去找人求助,這種思想用在程序開發的學習中,沒有什麼好處。筆者經常遇到學員問問題,結果有不少問題的答案在MSDN關於某個函數的解釋中就可看到(由於顯示器的限制,有的答案需要滾動窗口才能看到 J)。所以在這裏,筆者也建議讀者遇到問題一定要記得查看MSDN,學會使用MSDN並從中汲取知識,將使你受用無窮。

圖1.7   WinMain.exe的CPU佔用率接近100

回到正題,在1.4.3節介紹GetMessage函數時,曾說過如果hWnd參數是無效的窗口句柄或lpMsg參數是無效的指針時,GetMessage函數將返回-1。當我們關閉窗口時,調用了DestroyWindow來銷燬窗口,由於窗口被銷燬了,窗口的句柄當然也就是無效的句柄了,那麼GetMessage將返回-1。在C/C++語言中,非0即爲真,由於窗口被銷燬,句柄變爲無效,GetMessage總是返回-1,循環條件總是爲真,於是形成了一個死循環,機器當然就“變慢了”。J

在MSDN關於GetMessage函數的說明文檔中給出了下面的代碼:

BOOL bRet;

     while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)

     {

         if (bRet == -1)

         {

             // handle the error and possibly exit

         }

         else

         {

             TranslateMessage(&msg);

             DispatchMessage(&msg);

         }

     }

針對我們這個問題,可以修改上述代碼如下:

     HWND hwnd;

     hwnd=CreateWindow(…);

     …

     MSG msg;

     BOOL bRet;

     while( (bRet = GetMessage( &msg, hwnd, 0, 0 )) != 0)

     {

         if (bRet == -1)

         {

             // handle the error and possibly exit

            return -1;

         }

         else

         {

             TranslateMessage(&msg);

             DispatchMessage(&msg);

         }

     }



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章