Windows消息机制疑问探究
1.關(guān)于Windows中的系統(tǒng)消息循環(huán)占用CPU的疑問?
曾有這樣的疑問,為什么很多資料中都有關(guān)于windows中的While(getmessage(&msg,Null,0,0)){..}消息循環(huán)不占用CPU的說法?今天特有關(guān)此事查了一下資料,原來是這樣子啊! ? ???說,其實這里的while(){}循環(huán)是占用cpu的,只是getmessage()是一個阻塞型的函數(shù),當消息隊列中沒有消息時,它會檢查確認,當確認消息隊列為空時,則進行V操作,從而使線程外于阻塞狀態(tài),不被激發(fā),另外我們知道外于sleep狀態(tài)的線程是不占cpu的,是故當getmessage無返回值時,while()也不執(zhí)行。整個線程被阻塞,從而不占用CPU資源。 ???當Winows程序啟動時,會注冊一個窗口類,注冊的窗口類中包括當前窗口的風(fēng)格、消息處理函數(shù)等等。然后,程序創(chuàng)建一個該注冊窗口類的主窗口,接著,顯示這個主窗口并進入到消息循環(huán)。在消息循環(huán)中,將不斷地從窗口自身的消息隊列中讀取消息,并調(diào)用注冊的窗口消息處理函數(shù)對不同的消息進行處理。???????????GetMessage函數(shù)是一個阻塞型的函數(shù),當消息隊列中沒有消息時,GetMessage會處于阻塞狀態(tài)。一旦有消息到達,進程會被喚醒,GetMessage馬上返回。實現(xiàn)時,使用了一個信號量, GetMessage函數(shù)在確定沒有消息可讀時,對這個信號量進行一個V操作,從而使線程阻塞。而PostMessage、SendNotifyMessage、SendSyncMessage等任何一個發(fā)送消息函數(shù)在發(fā)送完消息之后,都會讀取這個信號量的值,當發(fā)現(xiàn)這個值等于零時,即表示讀消息的線程當前已阻塞,這時就會作一次P操作,來喚醒睡眠的線程。
2.**************************
理解消息循環(huán)和整個消息傳送機制對Windows編程十分重要。如果對消息處理的整個過程不了解,在windows編程中會遇到很多令人困惑的地方。
什么是消息(Message)
每個消息是一個整型數(shù)值,如果查看頭文件(查看頭文件了解API是一個非常好的習(xí)慣和普遍的做法)可以發(fā)現(xiàn)如下一些宏定義:
#define?WM_INITDIALOG?????????????????? 0x0110
#define?WM_COMMAND??????????????????????0x0111
#define?WM_LBUTTONDOWN??????????????????0x0201
//...
在 Windows通信中,至少一些基本W(wǎng)indows通信,幾乎都要用到消息。如果你想讓窗口或控件(實質(zhì)上,控件是特殊的窗口)執(zhí)行何種動作,你應(yīng)該傳送 一個消息給它;如果另一個窗口想讓你執(zhí)行何種操作,它可以傳送一個消息給你。如果一個事件,如敲擊鍵盤、移動鼠標、點擊按鈕等,系統(tǒng)將消息傳送給窗口,如 果你是這些窗口之一,你將接收到消息執(zhí)行相應(yīng)的操作。
每個Windows消息共有兩個參數(shù),wParam和lParam。最初的 wParam是16位(Win16時代)的,lParam是32位的。在Win32中,兩個參數(shù)都是32位的。并不是所有的消息都是用這兩個參數(shù),每個消 息使用它們的方式也不盡相同。如WM_CLOSE消息會忽略上述兩個參數(shù);再如WM_COMMAND消息使用上述兩個參數(shù),wParam包含”兩個” 值,HIWORD(wParam)是通知信息(如果可用),LOWORD(wParam)是發(fā)送消息的控件或菜單的ID,lParam是發(fā)送消息的控件的 HWND(窗口句柄),如果這個值為NULL,表示這個消息不是由控件發(fā)送的。
HIWORD()和LOWORD()是Windows定義的宏,分別取出一個32位整型值的高字和低字。在Win32中,一個”字”是一個16位整型,DWORD(Double WORD)是32位整型。
可 以用PostMessage()或SendMessage()發(fā)送消息。PostMessage()把一個消息放入消息隊列(Message Queue)后立即返回,也就是當調(diào)用PostMessage(),函數(shù)執(zhí)行完成返回時,很可能消息尚未處理。SendMessage()直接將消息發(fā)送 到窗口,直到這個消息處理完成才返回。如果要關(guān)閉一個窗口,可以給它發(fā)送一個WM_CLOSE消息,像PostMessage(hwnd, WM_CLOSE, 0, 0); 效果跟點擊窗口右上角的(關(guān)閉)按鈕是一樣的。注意這里的wParam和lParam的值都是0,因為前面提到過,WM_CLOSE消息會忽略上述兩個參數(shù)。
對話框(Dialogs)
如 果使用對話框,為跟控件通信,你需要向控件發(fā)送消息。你或者可以使用GetDlgItem()函數(shù)根據(jù)控件的ID取得控件的句柄,然后調(diào)用 SendMessage()函數(shù)發(fā)送消息;或者使用SendDlgItemMessage()組合了上面的步驟。傳入一個窗口句柄和子控件的ID能夠取得 子控件的句柄,用這個句柄發(fā)送消息。跟SendDlgItemMessage()類似的API如GetDlgItemText()能夠?qū)λ械拇翱谶M行操 作,而不僅僅是對話框。
什么是消息隊列(Message Queue)
假 設(shè)一個場景:系統(tǒng)正在處理WM_PAINT消息,就在這時用戶在鍵盤上敲擊了一些按鍵,這時會發(fā)生什么呢?系統(tǒng)應(yīng)該中斷繪圖操作然后處理按鍵消息還是應(yīng)該 丟棄按鍵的消息?很明顯這些都是不合理的,因此我們引入了消息隊列,當消息發(fā)送過來,將消息加入消息隊列,當一個消息被處理時,將其從消息隊列移除。這樣 確保消息不會丟失,當你正在處理一個消息時,其它到來的消息可以加入到消息隊列直到被處理。
什么是消息循環(huán)(Message Loop)
?
while(GetMessage(&Msg, NULL,?0,?0)?>?0)
{
????TranslateMessage(&Msg);
????DispatchMessage(&Msg);
}
上面代碼的執(zhí)行過程為:
1. 消息循環(huán)調(diào)用GetMessage()從消息隊列中查找消息進行處理,如果消息隊列為空,程序?qū)⑼V箞?zhí)行并等待(程序阻塞)。
2. 事件發(fā)生時導(dǎo)致一個消息加入到消息隊列(例如系統(tǒng)注冊了一個鼠標點擊事件),GetMessage()將返回一個正值,這表明有消息需要被處理,并且消息已經(jīng)填充到傳入的MSG參數(shù)中;當傳入WM_QUIT消息時返回0;如果返回值為負表明發(fā)生了錯誤。
3. 取出消息(在Msg變量中)并將其傳遞給TranslateMessage()函數(shù),這個函數(shù)做一些額外的處理:將虛擬鍵值信息轉(zhuǎn)換為字符信息。這一步實際上是可選的,但有些地方需要用到這一步。
4. 上面的步驟執(zhí)行完后,將消息傳遞給DispatchMessage()函數(shù)。DispatchMessage()函數(shù)將消息分發(fā)到消息的目標窗口,并且查找目標窗口過程函數(shù),給窗口過程函數(shù)傳遞窗口句柄、消息、wParam、lParam等參數(shù)然后調(diào)用該函數(shù)。
5. 在窗口過程函數(shù)中,檢查消息和其他參數(shù),你可以用它來實現(xiàn)你想要的操作。如果不想處理某些特殊的消息,你應(yīng)該總是調(diào)用DefWindowProc()函數(shù),系統(tǒng)將按按默認的方式處理這些消息(通常認為是不做任何操作)。
6. 一旦一個消息處理完成,窗口過程函數(shù)返回,DispatchMessage()函數(shù)返回,繼續(xù)循環(huán)處理下一個消息。
消 息循環(huán)對Windows編程來說是一個非常重要的概念。窗口過程函數(shù)并不是系統(tǒng)自動調(diào)用的,而是由開發(fā)人員自己通過調(diào)用 DispatchMessage()間接的調(diào)用的。如果你愿意,可以調(diào)用GetWindowLong()函數(shù)通過窗口句柄查找到窗口過程函數(shù)直接調(diào)用達到 消息處理的目的。
?
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編碼轉(zhuǎn)換、定時器回調(diào)等等都這樣的代碼都不適合,并且很可能導(dǎo)致很多打斷很多程序的正常運行。因此這樣的代碼在這里僅僅是試驗,真實項目中一定不能編寫這樣的代碼。
注 意這里我們用GetWindowLong()來獲得相關(guān)窗口的窗口過程函數(shù)。為什么我們不直接調(diào)用WndProc()函數(shù)呢?消息循環(huán)會處理程序中所有窗 口的消息,包括像按鈕、列表框等有他們自己的窗口過程函數(shù)的控件,因此我們要保證調(diào)用正確的窗口過程函數(shù)。盡管有時幾個窗口調(diào)用同一個窗口過程函數(shù),但函 數(shù)的第一個參數(shù) (窗口的句柄) 通常用于告知窗口過程函數(shù)是那個窗口發(fā)送的消息。
代碼可以看出,程序的大部分時間都在處理消息循環(huán)。窗 口會不斷的處理發(fā)過來的消息,但如果要退出程序該怎么做呢?因為我們用的是while()循環(huán),如果GetMessage()返回的是FALSE(即0) 會退出循環(huán),程序能夠執(zhí)行到WinMain()結(jié)束處,即程序退出:這正是PostQuitMessage()函數(shù)完成的工作,該函數(shù)會將WM_QUIT 消息添加到消息隊列的隊尾,GetMessage()從消息隊列取出WM_QUIT消息,填充Msg結(jié)構(gòu),返回的不是正數(shù),而是0。與此同時,結(jié)構(gòu)Msg 的成員wParam的值會被置為你傳給PostQuitMessage()函數(shù)參數(shù)的值,你可以選擇忽略它或做為WinMain()函數(shù)的返回值即進程的 退出代碼(Exit Code)。
注意:如果發(fā)生錯誤,GetMessage()函數(shù)將返回-1。你應(yīng)該記住這點,說不定你的程序會因此 出錯。盡管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()總是執(zhí)行成功,否則應(yīng)該使用下面這段代碼:
while(GetMessage(&Msg, NULL,?0,?0)?>?0)
總結(jié)
以上是生活随笔為你收集整理的Windows消息机制疑问探究的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅谈 JavaScript、ECMASc
- 下一篇: UI线程和Windows消息队列