MFC---理解Windows消息循環機制



最近在學習MFC,對Windows的消息循環機制弄得暈頭轉向,感受這篇文章不錯:html

      理解消息循環和整個消息傳送機制對Windows編程來講很是重要。若是對消息處理的整個過程不瞭解,在windows編程中會遇到不少使人困惑的地方。

什麼是消息(Message)

每一個消息是一個整型數值,若是查看頭文件(查看頭文件瞭解API是一個很是好的習慣和廣泛的作法)能夠發現以下一些宏定義:
編程

#define WM_INITDIALOG                   0x0110
#define WM_COMMAND                      0x0111

#define WM_LBUTTONDOWN                  0x0201
// ...

在Windows通訊中,至少一些基本Windows通訊,幾乎都要用到消息。若是你想讓窗口或控件(實質上,控件是特殊的窗口)執行何種動做,你應該傳送一個消息給它;若是另外一個窗口想讓你執行何種操做,它能夠傳送一個消息給你。若是一個事件,如敲擊鍵盤、移動鼠標、點擊按鈕等,系統將消息傳送給窗口,若是你是這些窗口之一,你將接收到消息執行相應的操做。

每一個Windows消息共有兩個參數,wParam和lParam。最初的wParam是16位(Win16時代)的,lParam是32位的。在Win32中,兩個參數都是32位的。並非全部的消息都是用這兩個參數,每一個消息使用它們的方式也不盡相同。如WM_CLOSE消息會忽略上述兩個參數;再如WM_COMMAND消息使用上述兩個參數,wParam包含」兩個」值,HIWORD(wParam)是通知信息(若是可用),LOWORD(wParam)是發送消息的控件或菜單的ID,lParam是發送消息的控件的HWND(窗口句柄),若是這個值爲NULL,表示這個消息不是由控件發送的。

HIWORD()和LOWORD()是Windows定義的宏,分別取出一個32位整型值的高字和低字。在Win32中,一個」字」是一個16位整型,DWORD(Double WORD)是32位整型。

能夠用PostMessage()或SendMessage()發送消息。PostMessage()把一個消息放入消息隊列(Message Queue)後當即返回,也就是當調用PostMessage(),函數執行完成返回時,極可能消息還沒有處理。SendMessage()直接將消息發送到窗口,直到這個消息處理完成才返回。若是要關閉一個窗口,能夠給它發送一個WM_CLOSE消息,像PostMessage(hwnd, WM_CLOSE, 0, 0); 效果跟點擊窗口右上角的 (關閉)按鈕是同樣的。注意這裏的wParam和lParam的值都是0,由於前面提到過,WM_CLOSE消息會忽略上述兩個參數。

對話框(Dialogs)

若是使用對話框,爲跟控件通訊,你須要向控件發送消息。你或者可使用GetDlgItem()函數根據控件的ID取得控件的句柄,而後調用SendMessage()函數發送消息;或者使用SendDlgItemMessage()組合了上面的步驟。傳入一個窗口句柄和子控件的ID可以取得子控件的句柄,用這個句柄發送消息。跟SendDlgItemMessage()相似的API如GetDlgItemText()可以對全部的窗口進行操做,而不只僅是對話框。

什麼是消息隊列(Message Queue)

假設一個場景:系統正在處理WM_PAINT消息,就在這時用戶在鍵盤上敲擊了一些按鍵,這時會發生什麼呢?系統應該中斷繪圖操做而後處理按鍵消息仍是應該丟棄按鍵的消息?很明顯這些都是不合理的,所以咱們引入了消息隊列,當消息發送過來,將消息加入消息隊列,當一個消息被處理時,將其從消息隊列移除。這樣確保消息不會丟失,當你正在處理一個消息時,其它到來的消息能夠加入到消息隊列直到被處理。

什麼是消息循環(Message Loop)

while (GetMessage( & Msg, NULL, 0 , 0 ) > 0 )
{
    TranslateMessage(
& Msg);
    DispatchMessage(
& Msg);
}

上面代碼的執行過程爲:
1. 消息循環調用GetMessage()從消息隊列中查找消息進行處理,若是消息隊列爲空,程序將中止執行並等待(程序阻塞)。
2. 事件發生時致使一個消息加入到消息隊列(例如系統註冊了一個鼠標點擊事件),GetMessage()將返回一個正值,這代表有消息須要被處理,而且消息已經填充到傳入的MSG參數中;當傳入WM_QUIT消息時返回0;若是返回值爲負代表發生了錯誤。
3. 取出消息(在Msg變量中)並將其傳遞給TranslateMessage()函數,這個函數作一些額外的處理:將虛擬鍵值信息轉換爲字符信息。這一步其實是可選的,但有些地方須要用到這一步。
4. 上面的步驟執行完後,將消息傳遞給DispatchMessage()函數。DispatchMessage()函數將消息分發到消息的目標窗口,而且查找目標窗口過程函數,給窗口過程函數傳遞窗口句柄、消息、wParam、lParam等參數而後調用該函數。
5. 在窗口過程函數中,檢查消息和其餘參數,你能夠用它來實現你想要的操做。若是不想處理某些特殊的消息,你應該老是調用DefWindowProc()函數,系統將按按默認的方式處理這些消息(一般認爲是不作任何操做)。
6. 一旦一個消息處理完成,窗口過程函數返回,DispatchMessage()函數返回,繼續循環處理下一個消息。

消息循環對Windows編程來講是一個很是重要的概念。窗口過程函數並非系統自動調用的,而是由開發人員本身經過調用DispatchMessage()間接的調用的。若是你願意,能夠調用GetWindowLong()函數經過窗口句柄查找到窗口過程函數直接調用達到消息處理的目的。

while (GetMessage( & Msg, NULL, 0 , 0 ) > 0 )
{
    WNDPROC fWndProc
= (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC);
    fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}
我嘗試着寫了上面的代碼,它確實能工做,但這裏存在各類問題,像Unicode/ANSI編碼轉換、定時器回調等等都這樣的代碼都不適合,而且極可能致使不少打斷不少程序的正常運行。所以這樣的代碼在這裏僅僅是試驗,真實項目中必定不能編寫這樣的代碼。

注意這裏咱們用GetWindowLong()來得到相關窗口的窗口過程函數。爲何咱們不直接調用WndProc()函數呢?消息循環會處理程序中全部窗口的消息,包括像按鈕、列表框等有他們本身的窗口過程函數的控件,所以咱們要保證調用正確的窗口過程函數。儘管有時幾個窗口調用同一個窗口過程函數,但函數的第一個參數 (窗口的句柄) 一般用於告知窗口過程函數是那個窗口發送的消息。

代碼能夠看出,程序的大部分時間都在處理消息循環。窗口會不斷的處理髮過來的消息,但若是要退出程序該怎麼作呢?由於咱們用的是while()循環,若是GetMessage()返回的是FALSE(即0)會退出循環,程序可以執行到WinMain()結束處,即程序退出:這正是PostQuitMessage()函數完成的工做,該函數會將WM_QUIT消息添加到消息隊列的隊尾,GetMessage()從消息隊列取出WM_QUIT消息,填充Msg結構,返回的不是正數,而是0。與此同時,結構Msg的成員wParam的值會被置爲你傳給PostQuitMessage()函數參數的值,你能夠選擇忽略它或作爲WinMain()函數的返回值即進程的退出代碼(Exit Code)。

注意:若是發生錯誤,GetMessage()函數將返回-1。你應該記住這點,說不定你的程序會所以出錯。儘管GetMessage()返回值位BOOL型,但它能夠返回TRUE或FALSE以外的值,由於BOOL被定義成UINT(unsigned int)。下面的程序貌似能正常工做,但有些時候不能正常工做。

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

while (GetMessage( & Msg, NULL, 0 , 0 ) != 0 )

while (GetMessage( & Msg, NULL, 0 , 0 ) == TRUE)

上面的代碼都是錯誤的!有些程序中你會看到會使用第一中方式,使用這種方式你必須保證GetMessage()老是執行成功,不然應該使用下面這段代碼:
while (GetMessage( & Msg, NULL, 0 , 0 ) > 0 )


但願你對Windows消息循環能有很好的理解,若是尚未,慢慢來,在使用過程當中會逐漸理解的。windows

中文原文:http://www.cnblogs.com/zxjay/archive/2009/06/27/1512372.html

英文原文:http://winprog.org/tutorial/message_loop.html
函數

相關文章
相關標籤/搜索