譯 理解Windows訊息迴圈

2022-04-15 09:11:06 字數 3762 閱讀 8012

出處:

理解訊息迴圈和整個訊息傳送機制對windows程式設計來說非常重要。如果對訊息處理的整個過程不了解,在windows程式設計中會遇到很多令人困惑的地方。

什麼是訊息(message)

#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) 

上面**的執行過程為:

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) 

我嘗試著寫了上面的**,它確實能工作,但這裡存在各種問題,像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訊息迴圈

今天跟同事聯調一段 被乙個問題鬱悶了很久。呼叫過程其實並不複雜,就是他提供乙個dll,並輸出乙個函式 姑且叫做foo 吧 我呼叫他的函式foo,其內部產生乙個視窗。但是,我每次呼叫,視窗總是一閃而過!我們於是懷疑,是不是因為主程式是console,不支援mfc的緣故?要不然一定是因為不是在主線程裡呼...

Windows的訊息佇列和訊息迴圈

一 windows中有乙個系統訊息佇列,對於每乙個正在執行的windows應用程式,系統為其建立乙個 訊息佇列 即應用程式佇列,用來存放該程式可能建立的各種視窗的訊息。應用程式中含有一段稱作 訊息迴圈 的 用來從訊息佇列中檢索這些訊息並把它們分發到相應的視窗函式中。二 windows為當前執行的每個...

詳談Windows訊息迴圈機制

一直對windows訊息迴圈不太清楚,今天做個詳細的總結,有說錯的地方,請務必指出。程式入口 intwinapi winmain 定義視窗類 typedef struct tagwndclassa 註冊視窗類 registerclass wndclass 生成視窗 createwindow 更新視窗...