日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > windows >内容正文

windows

windows消息机制详解-3

發(fā)布時(shí)間:2023/12/18 windows 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 windows消息机制详解-3 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1. 引言

Windows 在操作系統(tǒng)平臺(tái)占有絕對(duì)統(tǒng)治地位,基于Windows 的編程和開(kāi)發(fā)越來(lái)越廣泛。
Dos 是過(guò)程驅(qū)動(dòng)的,而Windows 是事件驅(qū)動(dòng)的[6],這種差別的存在使得很多Dos 程序員不能
習(xí)慣Windows 的程序開(kāi)發(fā)。而很多Windows 程序開(kāi)發(fā)人員也只是對(duì)消息運(yùn)行機(jī)制一知半解,
想要掌握Windows 編程的核心,必須深刻理解消息機(jī)制。事件驅(qū)動(dòng)圍繞著消息的產(chǎn)生與處
理展開(kāi),事件驅(qū)動(dòng)是靠消息循環(huán)機(jī)制來(lái)實(shí)現(xiàn)的。也可以理解為消息是一種報(bào)告有關(guān)事件發(fā)生
的通知,消息是Windows 操作系統(tǒng)的靈魂,掌握了消息運(yùn)行機(jī)制就掌握了Windows 編程的
神兵利器。本文將首先闡述Windows 的編程原理,繼而對(duì)Windows 的消息運(yùn)行機(jī)制進(jìn)行分
析,并講述對(duì)消息的處理。MFC 是一個(gè)廣為使用的編程類(lèi)庫(kù),對(duì)Windows 的消息機(jī)制進(jìn)行
了良好的封裝,所以,在第二部分將著重討論MFC 的消息映射,最后結(jié)合編程實(shí)際,通過(guò)
對(duì)MFC 消息映射的分析,非常巧妙的加以應(yīng)用,以幫助解決實(shí)際問(wèn)題

2. Windows 消息運(yùn)行機(jī)制
在介紹Windows 消息運(yùn)行機(jī)制之前,首先介紹一下消息的概念。
2.1 消息的概念和表示
消息(Message)指的就是Windows 操作系統(tǒng)發(fā)給應(yīng)用程序的一個(gè)通告[5],它告訴應(yīng)用
程序某個(gè)特定的事件發(fā)生了。比如,用戶(hù)單擊鼠標(biāo)或按鍵都會(huì)引發(fā)Windows 系統(tǒng)發(fā)送相應(yīng)
的消息。最終處理消息的是應(yīng)用程序的窗口函數(shù),如果程序不負(fù)責(zé)處理的話系統(tǒng)將會(huì)作出默
認(rèn)處理。
從數(shù)據(jù)結(jié)構(gòu)[4]的角度來(lái)說(shuō),消息是一個(gè)結(jié)構(gòu)體,它包含了消息的類(lèi)型標(biāo)識(shí)符以及其他的
一些附加信息。
系統(tǒng)定義的結(jié)構(gòu)體MSG[1]用于表示消息,MSG 具有如下定義形式:
typedef struct tagMSG
{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;

其中hwnd 是窗口的句柄,這個(gè)參數(shù)將決定由哪個(gè)窗口過(guò)程函數(shù)對(duì)消息進(jìn)行處理;message
是一個(gè)消息常量,用來(lái)表示消息的類(lèi)型;wParam 和lParam 都是32 位的附加信息,具體表
示什么內(nèi)容,要視消息的類(lèi)型而定;time 是消息發(fā)送的時(shí)間;pt 是消息發(fā)送時(shí)鼠標(biāo)所在的位
置。

2.2 Windows 編程原理
Windows 是一消息(Message)驅(qū)動(dòng)式系統(tǒng),Windows 消息提供了應(yīng)用程序與應(yīng)用程序
之間、應(yīng)用程序與Windows 系統(tǒng)之間進(jìn)行通訊的手段。應(yīng)用程序要實(shí)現(xiàn)的功能由消息來(lái)觸
發(fā),并靠對(duì)消息的響應(yīng)和處理來(lái)完成。Windows 系統(tǒng)中有兩種消息隊(duì)列,一種是系統(tǒng)消息隊(duì)
列,另一種是應(yīng)用程序消息隊(duì)列。計(jì)算機(jī)的所有輸入設(shè)備由 Windows 監(jiān)控,當(dāng)一個(gè)事件發(fā)
生時(shí),Windows 先將輸入的消息放入系統(tǒng)消息隊(duì)列中,然后再將輸入的消息拷貝到相應(yīng)的應(yīng)
用程序隊(duì)列中,應(yīng)用程序中的消息循環(huán)從它的消息隊(duì)列中檢索每一個(gè)消息并發(fā)送給相應(yīng)的窗
口函數(shù)中。一個(gè)事件的發(fā)生,到達(dá)處理它的窗口函數(shù)必須經(jīng)歷上述過(guò)程。
所謂消息就是描述事件發(fā)生的信息,Windows 程序是事件驅(qū)動(dòng)的,用這一方法編寫(xiě)程序
避免了死板的操作模式,因?yàn)閃indows 程序的執(zhí)行順序?qū)⑷Q于事件的發(fā)生順序,具有不
可預(yù)知性。Windows 操作系統(tǒng),計(jì)算機(jī)硬件,應(yīng)用程序之間具有如圖1 所示的關(guān)系

?

?

箭頭1 說(shuō)明操作系統(tǒng)能夠操縱輸入輸出設(shè)備,例如讓打印機(jī)打印;箭頭2 說(shuō)明操作系統(tǒng)
能夠感知輸入輸出設(shè)備的狀態(tài)變化,如鼠標(biāo)單擊,按鍵按下等,這就是操作系統(tǒng)和計(jì)算機(jī)硬
件之間的交互關(guān)系,應(yīng)用程序開(kāi)發(fā)者并不需要知道他們之間是如何做到的,我們需要了解的
操作系統(tǒng)與應(yīng)用程序之間如何交互。箭頭3 是應(yīng)用程序通知操作系統(tǒng)執(zhí)行某個(gè)具體的操作,
這是通過(guò)調(diào)用操作系統(tǒng)的API 來(lái)實(shí)現(xiàn)的;操作系統(tǒng)能夠感知硬件的狀態(tài)變化,但是并不決
定如何處理,而是把這種變化轉(zhuǎn)交給應(yīng)用程序,由應(yīng)用程序決定如何處理,向上的箭頭4
說(shuō)明了這種轉(zhuǎn)交情況,操作系統(tǒng)通過(guò)把每個(gè)事件都包裝成一個(gè)稱(chēng)為消息結(jié)構(gòu)體MSG 來(lái)實(shí)現(xiàn)
這個(gè)過(guò)程,也就是消息響應(yīng),要理解消息響應(yīng),首先需要了解消息的概念和表示。

2.3 Windows 消息循環(huán)
消息循環(huán)[1]是Windows 應(yīng)用程序存在的根本,應(yīng)用程序通過(guò)消息循環(huán)獲取各種消息,并
通過(guò)相應(yīng)的窗口過(guò)程函數(shù),對(duì)消息加以處理;正是這個(gè)消息循環(huán)使得一個(gè)應(yīng)用程序能夠響應(yīng)
外部的各種事件,所以消息循環(huán)往往是一個(gè)Windows 應(yīng)用程序的核心部分。
Windows 的消息機(jī)制如圖2 所示:

?

?

Windows 操作系統(tǒng)為每個(gè)線程維持一個(gè)消息隊(duì)列,當(dāng)事件產(chǎn)生時(shí),操作系統(tǒng)感知這一事
件的發(fā)生,并包裝成消息發(fā)送到消息隊(duì)列,應(yīng)用程序通過(guò)GetMessage()函數(shù)取得消息并存于
一個(gè)消息結(jié)構(gòu)體中,然后通過(guò)一個(gè)TranslateMessage()和DispatchMessage()解釋和分發(fā)消息,
下面的代碼描述了Windows 的消息循環(huán)。
while(GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
TranslateMessage(&msg)對(duì)于大多數(shù)消息而言不起作用,但是有些消息,比如鍵盤(pán)按鍵按
下和彈起(分別對(duì)于KeyDown 和KeyUp 消息),卻需要通過(guò)它解釋,產(chǎn)生一個(gè)WM_CHAR
消息。DispatchMessage(&msg)負(fù)責(zé)把消息分發(fā)到消息結(jié)構(gòu)體中對(duì)應(yīng)的窗口,交由窗口過(guò)程
函數(shù)處理。GetMessage()在取得WM_QUIT 之前的返回值都為T(mén)RUE,也就是說(shuō)只有獲取到
WM_QUIT 消息才返回FALSE,才能跳出消息循環(huán)。

2.4 消息的處理
取得的消息將交由窗口處理函數(shù)進(jìn)行處理,對(duì)于每個(gè)窗口類(lèi)Windows 為我們預(yù)備了一個(gè)
默認(rèn)的窗口過(guò)程處理函數(shù)DefWindowProc(),這樣做的好處是,我們可以著眼于我們感興趣
的消息,把其他不感興趣的消息傳遞給默認(rèn)窗口過(guò)程函數(shù)進(jìn)行處理。每一個(gè)窗口類(lèi)都有一個(gè)
窗口過(guò)程函數(shù),此函數(shù)是一個(gè)回調(diào)函數(shù),它是由Windows 操作系統(tǒng)負(fù)責(zé)調(diào)用的,而應(yīng)用程
序本身不能調(diào)用它。以switch 語(yǔ)句開(kāi)始,對(duì)于每條感興趣的消息都以一個(gè)case 引出。
LRESULT CALLBACK WndProc
(
HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam
)
{

switch(uMsgId)
{

case WM_TIMER://對(duì)WM_TIMER 定時(shí)器消息的處理過(guò)程
return 0;
case WM_LBUTTONDOWN://對(duì)鼠標(biāo)左鍵單擊消息的處理過(guò)程
reurn 0;
. …
default:
return DefWindowProc(hwnd,uMsgId,wParam,lParam);
}
}
對(duì)于每條已經(jīng)處理過(guò)的消息都必須返回0,否則消息將不停的重試下去;對(duì)于不感興趣
的消息,交給DefWindowProc()函數(shù)進(jìn)行處理,并需要返回其處理值。


3. MFC 的消息映射
MFC 是Windows 下編程的微軟基礎(chǔ)類(lèi)庫(kù),封裝了大部分Windows API 和Windows 控件,
提供了一套消息映射和命令響應(yīng)機(jī)制,方便了應(yīng)用程序的開(kāi)發(fā)。MFC 只是通過(guò)對(duì)Windows
消息映射的進(jìn)行封裝,使得添加消息響應(yīng)變得更為簡(jiǎn)單,但深究起來(lái),與Windows 消息機(jī)
制有一樣的底層實(shí)現(xiàn)。


3.1 MFC 消息映射的實(shí)現(xiàn)
在MFC 的框架結(jié)構(gòu)下,“消息映射”是通過(guò)巧妙的宏定義,形成一張消息映射表格來(lái)進(jìn)
行的。這樣一旦消息發(fā)生,Framework 就可以根據(jù)消息映射表格來(lái)進(jìn)行消息映射和命令傳遞。
首先在需要進(jìn)行消息處理的類(lèi)的頭文件(.H)里,都會(huì)含有DECLARE_MESSAGE_MAP()
宏,聲明該類(lèi)擁有消息映射表格。
然后在類(lèi)應(yīng)用程序文件(.CPP)實(shí)現(xiàn)這一表格
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)
//{{AFX_MSG_MAP(CInheritClass)
ON_COMMAND(ID_EDIT_COPY,OnEditCopy)
………
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
這里主要進(jìn)行消息映射的實(shí)現(xiàn),把它和消息處理函數(shù)聯(lián)系在一起。其中出現(xiàn)三個(gè)宏,第
一個(gè)宏是BEGIN_MESSAGE_MAP 有兩個(gè)參數(shù),分別是擁有消息表格的類(lèi),及其父類(lèi)。第
二個(gè)宏是ON_COMMAND , 指定命令消息的處理函數(shù)名稱(chēng)。第三個(gè)是
END_MESSAGE_MAP()作為結(jié)尾符號(hào)。
DECLARE_MESSAGE_MAP 宏定義里包含了MFC 定義的兩個(gè)新的數(shù)據(jù)結(jié)構(gòu);
AFX_MSGMAP_ENTRY 和AFX_MSGMAP;其中AFX_MSGMAP_ENTRY 結(jié)構(gòu)包含了
一個(gè)消息的所有相關(guān)信息,而AFX_MSGMAP 主要作用有兩個(gè),一是用來(lái)得到基類(lèi)的消息映
射入口地址。二是得到本身的消息映射入口地址。
實(shí)際上,MFC 把所有的消息一條條填入到AFX_MSGMAP_ENTRY 結(jié)構(gòu)中去,形成一
個(gè)數(shù)組,該數(shù)組存放了所有的消息和與它們相關(guān)的參數(shù)。同時(shí)通過(guò)AFX_MSGMAP 能得到
該數(shù)組的首地址,同時(shí)得到基類(lèi)的消息映射入口地址。當(dāng)本身對(duì)該消息不響應(yīng)的時(shí)候,就可
以上溯到基類(lèi)的消息映射表尋找對(duì)應(yīng)的消息響應(yīng)。
MFC 通過(guò)鉤子函數(shù)_AfxCbtFilterHook()截獲消息,并在此函數(shù)中把窗口過(guò)程函數(shù)設(shè)置為

AfxWindProc,而原來(lái)的窗口過(guò)程函數(shù)被保存在成員變量m_pfnSuper 中。
在MFC 框架下,通過(guò)下面的步驟來(lái)對(duì)消息進(jìn)行映射[7]。
1 函數(shù)AfxWndProc 接收Windows 操作系統(tǒng)發(fā)送的消息。
2 函數(shù)AfxWndProc 調(diào)用函數(shù)AfxCallWndProc 進(jìn)行消息處理,這里一個(gè)進(jìn)步是把對(duì)句柄的
操作轉(zhuǎn)換成對(duì)CWnd 對(duì)象的操作。
3 函數(shù)AfxCallWndProc 調(diào)用CWnd 類(lèi)的方法WindowProc 進(jìn)行消息處理。
4 WindowProc 調(diào)用OnWndMsg 進(jìn)行正式的消息處理,即把消息派送到相關(guān)的方法中去處理。
5 如果OnWndMsg 方法沒(méi)有對(duì)消息進(jìn)行處理的話,就調(diào)用DefWindowProc 對(duì)消息進(jìn)行處理。
這就是MFC 對(duì)消息調(diào)用過(guò)程的巧妙封裝。


3.2 MFC 消息分類(lèi)


1 命令消息(WM_COMMAND)
比如菜單項(xiàng)的選擇,工具欄按鈕點(diǎn)擊等發(fā)出該消息。所有派生自CCmdTarget 的類(lèi)都有
能力接收WM_COMMAND 消息。


2 標(biāo)準(zhǔn)消息(WM_XXX)
比如窗口創(chuàng)建,窗口銷(xiāo)毀等。所有派生自CWnd 的類(lèi)才有資格接收標(biāo)準(zhǔn)消息。


3 通告消息(WM_NOTIFY)
這是有控件向父窗口發(fā)送的消息,標(biāo)示控件本身狀態(tài)的變化。比如下拉列表框選項(xiàng)的改
變CBN_SELCHANGE 和樹(shù)形控件的TVN_SELCHANGED 消息都是通告消息。
Window 9x 版及以后的新控件通告消息不再通過(guò)WM_COMMAND 傳送,而是通過(guò)
WM_NOTIFY 傳送, 但是老控件的通告消息, 比如CBN_SELCHANGE 還是通過(guò)
WM_COMMAND 消息發(fā)送。


4 自定義消息
利用MFC 編程,可以使用自定義消息。使用自定義消息需要遵循一定的步驟[2]并需要
自己編寫(xiě)消息響應(yīng)函數(shù)


4. MFC 消息的靈活運(yùn)用
在此,我們給出一個(gè)示例程序,演示對(duì)MFC 消息的靈活運(yùn)用,通過(guò)此例的剖析,將加
深我們對(duì)MFC 消息的理解。


4.1 示例功能描述
本示例程序?qū)⒀菔具@樣一種效果:
對(duì)話框上有一個(gè)CTabCtrl 控件,一個(gè)CComboBox 控件,兩個(gè)按鈕Button1 和Button2。
CTabCtrl 控件有兩個(gè)標(biāo)簽頁(yè)Tab1 和Tab2;CComboxBox 有兩個(gè)選項(xiàng):選項(xiàng)1 和選項(xiàng)2;通
過(guò)按鈕(Button1 和Button2)單擊,分別發(fā)送CTabCtrl 控件的TCN_SELCHANGE 消息和
下拉列表框的CBN_SELCHANGE 消息,在各自的消息響應(yīng)函數(shù)中只是簡(jiǎn)單的對(duì)控件選項(xiàng)做
切換和給出提示信息。
單擊Button1 將選中標(biāo)簽頁(yè)Tab1 和下拉列表框的選項(xiàng)1,并彈出提示信息;單擊Button2
將選中標(biāo)簽頁(yè)Tab2 和下拉列表框的選項(xiàng)2,并彈出提示信息。


4.2 程序設(shè)計(jì)思路
TCN_SELCHANGE 消息和CBN_SELCHANGE 消息都屬于通告消息,此消息由子控件

發(fā)送給父窗口,在MSDN 中查詢(xún)發(fā)現(xiàn)TCN_SELCHANGE 消息是以WM_NOTIFY 消息的形
式發(fā)送,在MSDN 中查詢(xún)WM_NOTIFY 消息:
idCtrl = (int) wParam;
pnmh = (LPNMHDR) lParam;
也就是說(shuō),WPARAM 參數(shù)傳遞發(fā)送此消息的控件標(biāo)識(shí),LAPAM 參數(shù)一個(gè)指向NMHDR
結(jié)構(gòu)體的指針。NMHDR 結(jié)構(gòu)體定義如下:
typedef struct tagNMHDR
{
HWND hwndFrom;
UINT idFrom;
UINT code;
}
NMHDR; 其中hwndFrom 標(biāo)識(shí)發(fā)送消息控件的句柄,idFrom 是發(fā)送消息控件的ID,code
則是消息碼,如果要發(fā)送TCN_SELCHANGE 消息,則以TCN_SELCHANGE 填充。
查詢(xún)MSDN 發(fā)現(xiàn), 由CComboBox 控件發(fā)送的CBN_SELCHANGE 消息以
WM_COMMAND 消息發(fā)送,WPARAM 的高字節(jié)傳遞CComboBox 控件的ID,低字節(jié)發(fā)送
消息碼CBN_SELCHANGE,而LPARAM 則傳送發(fā)送此消息的控件句柄。
所以我們可以通過(guò)在按鈕控件的單擊響應(yīng)函數(shù)里分別發(fā)送WM_NOTIFY 和
WM_COMMAND 消息來(lái)引起TCN_SELCHANGE 和CBN_SELCHANGE 消息響應(yīng)函數(shù)的調(diào)
用,分別在兩控件消息響應(yīng)函數(shù)中實(shí)現(xiàn)選項(xiàng)改變和消息提示即可,遵照這種思路,我們就可
以實(shí)現(xiàn)我們想要的功能。


4.3 程序?qū)崿F(xiàn)步驟
啟動(dòng)VC++6.0,新建基于對(duì)話框的應(yīng)用程序MsgTest.
在對(duì)話框上添加1 個(gè)CTabCtrl 控件,一個(gè)CComboBox 控件,2 個(gè)按鈕Button1 和Button2;
給IDC_TAB1 和IDC_COMBO1 分別關(guān)聯(lián)控件成員變量m_tab1 和m_cb1;為兩按鈕分
別添加按鈕單擊響應(yīng)函數(shù)。
在對(duì)話框的OnInitDlg()函數(shù)中為CTabCtrl 控件添加兩個(gè)標(biāo)簽頁(yè),Tab1 和Tab2;為
ComboBox 添加選項(xiàng)1 和2;代碼如下:
m_tab1.InsertItem(0,"Tab1");
m_tab1.InsertItem(1,"Tab2");
m_cb1.AddString("選項(xiàng)1");
m_cb1.AddString("選項(xiàng)2");
用ClassWizard 為CTabCtrl 添加消息響應(yīng)TCN_SELCHANGE,為CComboBox 添加消息
響應(yīng)CBN_SELCHANGE。在OnSelchangeTab1()函數(shù)中添加代碼
int nIndex=m_tab1.GetCurSel();
CString str;
str.Format("%d",nIndex+1);
MessageBox("Tab"+str+" selected!");
在OnSelchangeCombo1()函數(shù)中添加代碼:
int nIndex=m_cb1.GetCurSel();
CString str;
str.Format("%d",nIndex+1);

MessageBox("ComboBox 選項(xiàng)"+str+" selected!");
在按鈕1 的響應(yīng)函數(shù)OnButton1()中添加代碼:
m_tab1.SetCurSel(0);
NMHDR nmhdr;
nmhdr.code=TCN_SELCHANGE;
nmhdr.hwndFrom=GetDlgItem(IDC_TAB1)->m_hWnd;
nmhdr.idFrom=IDC_TAB1;
SendMessage(WM_NOTIFY,(WPARAM)IDC_TAB1,(LPARAM)&nmhdr);
m_cb1.SetCurSel(0);
WPARAM wParam=0;
WPARAM lParam=0;
wParam=IDC_COMBO1;
wParam= wParam | (CBN_SELCHANGE<<16);
lParam=(WPARAM)(GetDlgItem(IDC_COMBO1)->m_hWnd);
SendMessage(WM_COMMAND, wParam, lParam);
在按鈕2 的響應(yīng)函數(shù)OnButton2()中添加類(lèi)似代碼,只需要把m_tab1.SetCurSel(0)和
m_cb1.SetCurSel(0)分別改成m_tab1.SetCurSel(1)和m_cb1.SetCurSel(1)。
通過(guò)SendMessage() 函數(shù)向控件的父窗口也就是對(duì)話框窗口發(fā)送相應(yīng)的消息,
TCN_SELCHANGE 是以WM_NOTIFY 消息的形式發(fā)送,參數(shù)WPARAM 標(biāo)識(shí)發(fā)送
TCN_SELCHANGE 消息的控件ID,LPARAM 是一個(gè)NMHDR 結(jié)構(gòu)體的指針,此結(jié)構(gòu)體的
成員code 標(biāo)識(shí)發(fā)送什么通告消息,此處是TCN_SELCHANGE,hwndFrom 是發(fā)送消息的控
件句柄, 程序中用GetDlgItem()->m_hWdn 獲得, idFrom 是發(fā)送消息的控件ID 。
CBN_SELCHANGE 以WM_COMMAND 消息的形式發(fā)送,同樣的,通過(guò)查閱MSDN,可以
對(duì)此消息的兩個(gè)參數(shù)進(jìn)行賦值,以保證消息的正確發(fā)送。
通過(guò)上面的5 個(gè)步驟,我們的程序就編寫(xiě)完成了,單擊Button1,可以發(fā)現(xiàn),CTabCtrl
切換到了Tab1 標(biāo)簽頁(yè),CComboBox 選擇了“選項(xiàng)1”,并彈出消息對(duì)話框。由此可見(jiàn)確實(shí)引
起了消息響應(yīng)函數(shù)的調(diào)用,完成了預(yù)定的功能。
通過(guò)查閱MSDN,可以得到其他消息的發(fā)送和包裝形式,我們可以方便的加以利用,完
成更為復(fù)雜的功能,可以說(shuō),掌握了Windows 的消息機(jī)制,就掌握了Windows 編程的核心。
5. 總結(jié)
Windows 消息機(jī)制是Windows 編程的本質(zhì)和核心,對(duì)Windows 消息機(jī)制的理解能提高
我們Windows 程序開(kāi)發(fā)的能力。本文首先闡述Windows 的消息機(jī)制,然后講解了MFC 的
消息映射,消息分類(lèi),最后通過(guò)示例程序,講解如何借助MSDN,靈活運(yùn)用消息編程,解
決實(shí)際問(wèn)題。本文對(duì)Windows 下的程序開(kāi)發(fā)具有一定的參考和借鑒意義。

/********************************

1.???????Windows 的歷史

中國(guó)人喜歡以史為鑒,而事實(shí)也確實(shí)是,如果你能知道一件事情的來(lái)龍去脈,往往可以更容易地理解事物為什么會(huì)表現(xiàn)為當(dāng)前這樣的現(xiàn)狀。所以,我的介紹性開(kāi)場(chǎng)白通常會(huì)以一段歷史開(kāi)始。不過(guò),我不會(huì)以精確到年月日的那種方式詳細(xì)講述,而是選取幾個(gè)對(duì)我們的編程生涯有重要影響的關(guān)鍵點(diǎn)。

Windows 是真正的圖形化界面操作系統(tǒng)的普及者,無(wú)論任何人,爭(zhēng)奪什么第一個(gè)實(shí)現(xiàn)的GUI、第一個(gè)商業(yè)化的GUI之類(lèi)的虛名,都替代不了 Windows 的歷史功績(jī),讓最普通的用戶(hù)能夠容易地操縱PC。

第一個(gè)聲名大噪的版本是Windows 3.0(也有人認(rèn)為應(yīng)該是它的更加健康強(qiáng)壯的弟弟Windows 3.1),從那個(gè)時(shí)候開(kāi)始,我們就和本文中以下的幾個(gè)關(guān)鍵角色有了不盡的情緣:

[cpp]??view plain?copy

  • while(GetMessage(&msg,?NULL,0,?0))??
  • {??
  • ???????????????TranslateMessage(&msg);??
  • ???????????????DispatchMessage?(&msg);??
  • }??
  • while(GetMessage(&msg, NULL,0, 0))

  • {

  • TranslateMessage(&msg);

  • DispatchMessage (&msg);

  • }

  • 上面代碼中的這三個(gè)相關(guān)函數(shù),會(huì)在后文中提到。

    第二個(gè)大紅大紫的版本則非Windows 95莫屬。這個(gè)版本的主要變化在于,無(wú)論如何,它是一個(gè)大眾化的所謂32位系統(tǒng)了。之所以要加上“所謂的”三個(gè)字,是因?yàn)檫@個(gè)系統(tǒng)是個(gè)混血兒,在32位代碼中混雜有大量的從之前的Windows3.x上移植過(guò)來(lái)的16位代碼。

    此時(shí)間稍后,另一支潛力股的關(guān)鍵進(jìn)化過(guò)程結(jié)束,Windows NT 4.0隆重登場(chǎng),這個(gè)分支的操作系統(tǒng)是全32位的,成為了 Windows 95 系列的掘墓者,也是我們現(xiàn)在所使用幾乎所有的 Windows 桌面系統(tǒng)(Windows2000/XP/2003/Vista/2008)的前輩。但是,這個(gè)版本由于對(duì)系統(tǒng)硬件的要求甚高(在當(dāng)時(shí)),所以沒(méi)有引起普通用戶(hù)的廣泛關(guān)注。

    下一個(gè)里程碑就是Windows 2000了,微軟實(shí)現(xiàn)了Windows9x/Me分支和Windows NT分支的合并。緊接著,Windows XP 現(xiàn)身。從有關(guān)消息方面來(lái)考察,Windows2000 做了微小的改進(jìn),在此之前,我們?cè)诤芏嗲闆r下需要?jiǎng)?chuàng)建一個(gè)正常的、隱藏的、完整的窗口來(lái)處理消息,而 Windows 2000 引入了一種特殊類(lèi)型的窗口用于此類(lèi)需求。道理上來(lái)講,應(yīng)該會(huì)減少一些資源占用。

    此后經(jīng)過(guò)五六年的時(shí)間,Windows Vista誕生。事實(shí)上,從 Windows2000 開(kāi)始,Windows 家族的編程模型,尤其是對(duì)原生態(tài)代碼(native code)而言,已經(jīng)基本沒(méi)有太大的變化。通常只是增加了新的API或者用戶(hù)控件,或者現(xiàn)有控件增加了新的功能或者風(fēng)格。盡管 Windows Vista 中有很多的變化,但是對(duì)于我們今天要講到的主題,影響不大。最主要的一個(gè)影響,是消息的發(fā)送方和接收方之間有了等級(jí)限制,不像之前可以隨意互相進(jìn)行消息傳遞,這是出于安全性的考慮。

    2.???????Windows 的宏觀構(gòu)造

    從最原始的版本開(kāi)始,有三個(gè)比較大的功能塊占據(jù)了Windows系統(tǒng)的絕大部分,這三個(gè)塊,就是赫赫有名的Kernel、GDI、User。從Windows 95起,另兩個(gè)在先前不太起眼的部分也迅速崛起,那就是大名鼎鼎的Registry和Shell。

    這幾個(gè)大塊的分工是這樣的:Kernel,望文生義,負(fù)責(zé)內(nèi)核部分,這是任何一個(gè)可以稱(chēng)之為操作系統(tǒng)的東西的基石,主要職責(zé)有:內(nèi)存管理、任務(wù)調(diào)度、外設(shè)管理等;GDI,則是對(duì)可以進(jìn)行圖形化操縱的設(shè)備的操作接口,對(duì)外提供的主要功能是在設(shè)備上:提供坐標(biāo)系統(tǒng),繪制點(diǎn)、線、形狀,進(jìn)行填充,文本繪制,管理畫(huà)筆、畫(huà)刷、字體等繪圖對(duì)象;User,則是前兩者的粘合劑,使系統(tǒng)能夠通過(guò)圖形化操作方式和使用者(也就是User)進(jìn)行交互,把零散的GDI對(duì)象有機(jī)地組織起來(lái),抽象為窗口,用以接受用戶(hù)的輸入,進(jìn)行相應(yīng)的運(yùn)算(廣義上的,并不是局限于算數(shù)運(yùn)算),并最終將結(jié)果呈現(xiàn)給用戶(hù)。當(dāng)然,User 部分通常是指可以實(shí)現(xiàn)上述的功能的基礎(chǔ)構(gòu)造,真正的實(shí)現(xiàn)部分需要大量的額外工作,這也是 Shell 部分的主要工作。而Registry,則是提供給用戶(hù)一種與物理存儲(chǔ)無(wú)關(guān)的統(tǒng)一的數(shù)據(jù)訪問(wèn)方式。

    很容易就可以看出,消息功能,這種被我們一直以窗口間通訊最為自然的方式所使用的機(jī)制,應(yīng)該隸屬于 User 部分。

    對(duì)于 Windows Mobile 系統(tǒng)來(lái)說(shuō),底層的實(shí)現(xiàn)上與桌面系統(tǒng)大相徑庭,例如,它本身并沒(méi)有kernel32.dll、gdi32.dll、user32.dll這幾個(gè)眾所周知的系統(tǒng)庫(kù),而是有一個(gè)多合一的coredll.dll,而且內(nèi)核被實(shí)現(xiàn)為一個(gè)更接近于正常進(jìn)程的nk.exe進(jìn)程,而不是桌面系統(tǒng)下的那個(gè)抽象的執(zhí)行體。盡管如此,但是在邏輯上,我們依然可以將之與桌面系統(tǒng)同等看待。

    3.???????Windows 的消息概念

    在我們的通常認(rèn)識(shí)上,消息事實(shí)就是一個(gè)數(shù)值。我們檢查一下消息相關(guān)的各個(gè)回調(diào)函數(shù)的原型就會(huì)發(fā)現(xiàn),表示消息的那個(gè)參數(shù)的數(shù)據(jù)類(lèi)型是 UINT,也就是無(wú)符號(hào)的整數(shù)類(lèi)型。不過(guò),我們通常也會(huì)發(fā)現(xiàn),消息往往還附帶有兩個(gè)其他類(lèi)型的數(shù)據(jù),一個(gè)是 WPARAM 類(lèi)型的,一個(gè)是 LPARAM 類(lèi)型的,如果算上消息的目標(biāo)窗口的句柄,那么,一個(gè)消息以及相關(guān)信息才能夠說(shuō)是比較完整。為什么說(shuō)是比較呢?看一下 MSG 這個(gè)結(jié)構(gòu)的定義就會(huì)發(fā)現(xiàn),其實(shí)還有另外兩個(gè)我們不太經(jīng)常使用的數(shù)據(jù),是與一條消息有關(guān)系的。MSG 的完整聲明如下:

    [cpp]??view plain?copy

  • typedef?struct?{??
  • ????HWND?hwnd;??
  • ????UINT?message;??
  • ????WPARAM?wParam;??
  • ????LPARAM?lParam;??
  • ????DWORD?time;??
  • ????POINT?pt;??
  • }?MSG,?*PMSG;??
  • typedef struct {

  • HWND hwnd;

  • UINT message;

  • WPARAM wParam;

  • LPARAM lParam;

  • DWORD time;

  • POINT pt;

  • } MSG, *PMSG;

  • 前四項(xiàng)正是我們已經(jīng)提及過(guò)的,而后兩項(xiàng),一個(gè)表示消息發(fā)生時(shí)的時(shí)間,一個(gè)表示此消息發(fā)生時(shí)的按屏幕坐標(biāo)表示的鼠標(biāo)光標(biāo)的位置。

    從這個(gè)結(jié)構(gòu)也可以看出,我們經(jīng)常所說(shuō)的消息,更多是指代表了一個(gè)確定的消息的數(shù)值。

    我們可能還會(huì)聽(tīng)到有這樣的稱(chēng)呼:命令消息、通知消息、反射消息等等。首先需要聲明的一點(diǎn)是,這并不是對(duì) Windows 系統(tǒng)中的消息的科學(xué)分類(lèi),而是在某些特定場(chǎng)景下的通俗稱(chēng)謂。命令消息,一般特指 WM_COMMAND 消息,此消息通常由控件或者菜單發(fā)出,表示用戶(hù)執(zhí)行/發(fā)出了一個(gè)命令。通知消息,一般特指WM_NOTIFY 消息,此消息通常由公用控件(CommonControls)發(fā)出,表示一些事件發(fā)生了,需要處理。反射消息,一般用于對(duì) Windows API 的封裝類(lèi)或者類(lèi)庫(kù)中。這是一類(lèi)消息的總稱(chēng),它們的處理需要經(jīng)過(guò)一種被稱(chēng)為“反射”的機(jī)制。這一機(jī)制的具體方式下一節(jié)中會(huì)有描述。

    Windows 的消息分類(lèi)不好分(如果非要?jiǎng)澐值脑?#xff0c;可以分為系統(tǒng)定義的消息和應(yīng)用程序定義的消息),不過(guò)有一個(gè)區(qū)段劃分。從 0x0000 到 0x03FF,為系統(tǒng)定義的消息,常見(jiàn)的 WM_PAINT、WM_CREATE 等均在其中;從 0x0400 到 0x7FFF,專(zhuān)用于用戶(hù)自定義的消息,可以使用 WM_USER + x 的形式自行定義,其中WM_USER 的值就是 0x0400,x 取一個(gè)整數(shù);從 0x8000 到 0xBFFF,從 Windows 95 開(kāi)始,也用作用戶(hù)自定義的消息范圍,可以使用 WM_APP + x 的形式自行定義。根據(jù)微軟的建議,WM_APP類(lèi)消息用于程序之間的消息通信,而 WM_USER 類(lèi)消息則最好用于某個(gè)特定的窗口類(lèi)。微軟自己遵循這一慣例,所以,公用控件的消息,如 TVM_DELETEITEM,基本都是 WM_USER 類(lèi)屬。從 0xC000 開(kāi)始,到 0xFFFF,這個(gè)區(qū)段的消息值保留給 RegisterWindowMessage 這個(gè) API,此 API 可以接受一個(gè)字符串,把它變換成一個(gè)唯一的消息值。在桌面系統(tǒng)上,最常見(jiàn)的源字符串,可能就是“TaskbarCreated”了,由它對(duì)應(yīng)的消息會(huì)發(fā)送到所有的頂級(jí)窗口,通知任務(wù)欄剛剛被創(chuàng)建(可能是由于資源管理崩潰后重新啟動(dòng)導(dǎo)致的)。

    由上也可以看出,Windows 的消息值是一個(gè) 16 位的數(shù)字,這是 16 系統(tǒng)時(shí)代留給我們的痕跡。另外的一個(gè)痕跡是WPARAM 和 LPARAM 這兩個(gè)數(shù)據(jù)類(lèi)型,在 16 位時(shí)代,WPARAM 是 16 位的,其名字的意思是 wordparameter,LPARAM 是 32 位的,其名字的意思是 longparameter。

    4.???????Windows 的消息機(jī)制

    4.1.???????消息隊(duì)列

    說(shuō)到消息機(jī)制,可能連最初級(jí)的 Windows 程序員都會(huì)對(duì)消息隊(duì)列(MessageQueue)這個(gè)名詞耳熟(不過(guò)不見(jiàn)得能詳)。對(duì)于這樣一個(gè)基本概念,Windows 操作系統(tǒng)提供的針對(duì)消息隊(duì)列的API 卻少的可憐(GetQueueStatus、GetInputState、GetMessageExtraInfo、SetMessageExtraInfo),而且,這些 API 的出鏡率也相當(dāng)?shù)牡?#xff0c;甚至有不少經(jīng)驗(yàn)豐富的程序員也從來(lái)沒(méi)有使用過(guò)它們。在 Windows Mobile 上,這些 API 干脆付諸闕如,不過(guò)有一個(gè)同樣極少使用的GetMessageQueueReadyTimeStamp 函數(shù)在充門(mén)面。

    這一切,都?xì)w功于在 API 層極好的封裝性,減少了開(kāi)始接觸這個(gè)平臺(tái)時(shí)需要了解的概念。但是,對(duì)于我們這樣既想知其然,又想知其所以然的群體,還是有必要對(duì)消息隊(duì)列有充分的了解。

    4.1.1.??????系統(tǒng)消息隊(duì)列

    這是一個(gè)系統(tǒng)唯一的隊(duì)列,輸入設(shè)備(鍵盤(pán)、鼠標(biāo)或者其他)的驅(qū)動(dòng)程序會(huì)把用戶(hù)的操作輸入轉(zhuǎn)化成消息放置于系統(tǒng)隊(duì)列中,然后系統(tǒng)會(huì)把此消息轉(zhuǎn)到目標(biāo)窗口所在線程的消息隊(duì)列中等待處理。

    4.1.2.??????線程消息隊(duì)列(應(yīng)用程序消息隊(duì)列)

    應(yīng)用程序消息隊(duì)列這個(gè)名稱(chēng)是歷史遺留,在 32 位(以及之后的 64 位)系統(tǒng)中,正確的名稱(chēng)應(yīng)該是線程消息隊(duì)列。每一個(gè)GUI線程都會(huì)維護(hù)這樣一個(gè)線程消息隊(duì)列。(這個(gè)隊(duì)列只有在線程調(diào)用 User 或者 GDI 函數(shù)時(shí)才會(huì)創(chuàng)建,默認(rèn)并不創(chuàng)建)。然后線程消息隊(duì)列中的消息會(huì)被本線程的消息循環(huán)(有時(shí)也被稱(chēng)為消息泵)派送到相應(yīng)的窗口過(guò)程(也叫窗口回調(diào)函數(shù))處理。

    4.2.???????消息的生命期

    4.2.1.??????消息的產(chǎn)生

    消息產(chǎn)生的源頭有兩個(gè),一個(gè)是系統(tǒng),一個(gè)是應(yīng)用程序。系統(tǒng)產(chǎn)生的消息又可以大致分為兩類(lèi),一類(lèi)是由輸入設(shè)備導(dǎo)致的,例如 WM_MOUSEMOVE,一類(lèi)是User部分(或者是系統(tǒng)內(nèi)的其他部分通過(guò)User部分)為了實(shí)現(xiàn)自身的正常行為或者管理功能而主動(dòng)生成的,如 WM_WINDOWPOSCHANGED。

    產(chǎn)生的方式也有兩種,一種稱(chēng)為發(fā)送(Send),另一種稱(chēng)為投遞(Post,也有譯作張貼的),對(duì)應(yīng)于大家極為熟悉的兩個(gè) API,SendMessage 和 PostMessage。系統(tǒng)產(chǎn)生的消息,雖然我們看不到代碼,不過(guò)我們還是可以粗略地劃撥一下,基本上所有的輸入類(lèi)消息,都是以投遞的方式抵達(dá)應(yīng)用的,而其他的消息,則大部分是采取了發(fā)送方式。

    至于應(yīng)用程序,可以隨意選用適合自己的消息產(chǎn)生方式。

    4.2.2.??????消息的處理

    在絕大部分情況下,消息總是有一個(gè)目標(biāo)窗口的,因此,消息也絕大部分是被某個(gè)窗口所處理的。處理消息的地方,就是這個(gè)窗口的回調(diào)函數(shù)。

    窗口的回調(diào)函數(shù),之所以被稱(chēng)作“回調(diào)”,就是因?yàn)檫@個(gè)函數(shù)一般并不是由用戶(hù)(程序員)主動(dòng)調(diào)用它的,而是系統(tǒng)認(rèn)為在恰當(dāng)?shù)臅r(shí)候?qū)λM(jìn)行調(diào)用。那么,這個(gè)“恰當(dāng)?shù)臅r(shí)候”是什么時(shí)候呢?根據(jù)消息產(chǎn)生的方式,“恰當(dāng)?shù)臅r(shí)候”也有兩個(gè)時(shí)機(jī)。

    第一個(gè)時(shí)機(jī)是,DispatchMessage 函數(shù)被調(diào)用時(shí),另一個(gè)時(shí)機(jī)是SendMessage 函數(shù)被調(diào)用時(shí)。

    我們正常情況下以系統(tǒng)處理對(duì)一個(gè)頂級(jí)窗口的關(guān)閉按鈕的鼠標(biāo)左鍵點(diǎn)擊事件為例來(lái)說(shuō)明。

    這個(gè)點(diǎn)擊事件完成的標(biāo)志性消息是 WM_NCLBUTTONUP,表示在一個(gè)窗口的非客戶(hù)區(qū)的鼠標(biāo)左鍵釋放動(dòng)作,另外,這個(gè)鼠標(biāo)消息的其他數(shù)據(jù)中會(huì)表明,發(fā)生這個(gè)動(dòng)作的位置是在關(guān)閉按鈕上(HTCLOSE)。這是一個(gè)鼠標(biāo)輸入事件,從前文可以知道,它會(huì)被系統(tǒng)投遞到消息隊(duì)列中。

    于是,在消息循環(huán)中GetMessage 的某次執(zhí)行結(jié)束后,這個(gè)消息被取到了 MSG 結(jié)構(gòu)里。從文章開(kāi)頭的消息循環(huán)代碼可知,這個(gè)消息接下來(lái)會(huì)被 TranslateMessage 函數(shù)做必要的(事實(shí)上是“可能的”)翻譯,然后交給 DispatchMessage 來(lái)全權(quán)處理。

    DispatchMessage 拿到了 MSG 結(jié)構(gòu),開(kāi)始自己的一套辦事流程。

    首先,檢查消息指定的目標(biāo)窗口句柄。看系統(tǒng)內(nèi)(實(shí)際上是本線程內(nèi))是不是確實(shí)存在這樣一個(gè)窗口,如果沒(méi)有,那說(shuō)明這個(gè)消息已經(jīng)不會(huì)有需要對(duì)它負(fù)責(zé)的人選了,那么這個(gè)消息就會(huì)被丟棄。

    如果有,它就會(huì)直接調(diào)用目標(biāo)窗口的回調(diào)函數(shù)。終于看到,我們寫(xiě)的回調(diào)函數(shù)出場(chǎng)了,這就是“恰當(dāng)?shù)臅r(shí)機(jī)”之一。當(dāng)然,為了敘述清晰,此處省略了系統(tǒng)做的一些其他處理。

    這樣,對(duì)于系統(tǒng)來(lái)說(shuō),一條投遞消息就處理完成,轉(zhuǎn)而繼續(xù) GetMessage。

    不過(guò)對(duì)于我們上面的例子,事情還沒(méi)有完。

    我們都清楚,對(duì)于 WM_NCLBUTTONUP 這樣一條消息,通常我們是無(wú)暇去做額外處理的(正事還忙不過(guò)來(lái)呢……)。所以,我們一般都會(huì)把它扔到那個(gè)著名的垃圾堆里,沒(méi)錯(cuò),DefWindowProc。盡管如此,我們還是可以看出,DefWindowProc其實(shí)已經(jīng)成了我們的回調(diào)函數(shù)的一個(gè)組成部分,唯一的差別在,這個(gè)函數(shù)不是我們自己寫(xiě)的而已。

    DefWindowProc 對(duì)這個(gè)消息的處理也是相當(dāng)輕松,它基本上沒(méi)有做什么實(shí)質(zhì)性的事情,而是生成了另外一個(gè)消息,WM_SYSCOMMAND,同時(shí)在 wParam 里指定為 SC_CLOSE。這一次,消息沒(méi)有被投遞到消息隊(duì)列里,而是直接 Send 出來(lái)的。

    于是,SendMessage 的艱難歷程開(kāi)始。

    第一步,SendMessage 的方向和DispatchMessage 幾乎一模一樣,檢查句柄。

    第二步,事情就來(lái)了,它需要檢查目標(biāo)窗口和自己在不在一個(gè)線程內(nèi)。如果在,那就比較好辦,按照 DispatchMessage 趟出來(lái)的老路走:調(diào)用目標(biāo)窗口的回調(diào)函數(shù)。這,就是“恰當(dāng)?shù)臅r(shí)機(jī)”之二。

    可是要是不在一個(gè)線程內(nèi),那就麻煩了。道理很簡(jiǎn)單,別的線程有自己的運(yùn)行軌跡,沒(méi)有辦法去讓它立即就來(lái)處理這個(gè)消息。

    現(xiàn)在,SendMessage該怎么處理手里的這個(gè)燙手山芋呢?(作者注:寫(xiě)到此處時(shí),很有寫(xiě)上“欲知后事如何,且聽(tīng)下回分解”的沖動(dòng))

    微軟的架構(gòu)師做了個(gè)非常聰明的選擇:不干涉其他線程的內(nèi)政。我不會(huì)生拉硬拽讓你來(lái)處理我的消息,我會(huì)把消息投遞給你(這個(gè)投遞是內(nèi)部操作,從外面看,這條消息應(yīng)該一直被認(rèn)為是發(fā)送過(guò)去的),然后—— 我等著。

    這下,球踢到了目標(biāo)線程那邊。目標(biāo)線程一點(diǎn)也不含糊,既然消息來(lái)到了我的隊(duì)列里,那我的 GetMessage 會(huì)按照既定的流程走,不過(guò),和上文WM_NCLBUTTONUP 的經(jīng)歷有所不同。鑒于這條消息是外來(lái)客,而且是Send 方式,于是它以?xún)?yōu)先于線程內(nèi)部的其他消息進(jìn)行處理(畢竟友邦在等著啊),處理完畢之后,把結(jié)果返回給消息的源線程。可以參見(jiàn)下文中對(duì) GetMessage 函數(shù)的敘述。

    在我們的現(xiàn)在討論的這個(gè)例子里,由于 SendMessage(WM_SYSCOMMAND) 是屬于本線程內(nèi)的,所以就會(huì)遞歸調(diào)用回窗口的回調(diào)函數(shù)里。此后的處理,還是另外的幾個(gè)消息被衍生出來(lái),如 WM_CLOSE 和 WM_DESTROY。這個(gè)例子僅僅出于概念性的展示,而不是完全精確可靠的,而且,在 Windows Mobile 上,干脆就沒(méi)有非客戶(hù)區(qū)的概念。

    這就是系統(tǒng)內(nèi)所有消息的處理方式。

    不過(guò)稍等,PostThreadMessage 投遞到消息隊(duì)列里的消息怎么辦?答案是:你自己看著辦。最好的處理位置,就是在消息循環(huán)中的TranslateMessage 調(diào)用之前。

    另外一個(gè)需要稍做注解的問(wèn)題是消息的返回值問(wèn)題,這個(gè)問(wèn)題有些微妙。對(duì)于大多數(shù)的消息,返回值都沒(méi)有什么意義。對(duì)于另外的一些消息,返回值意義重大。我相信有很多人對(duì) WM_ERASEBKGND 消息的返回值會(huì)有印象,該消息的返回值直接影響到系統(tǒng)是不是要進(jìn)行缺省的繪制窗口背景操作。所以,處理完一條消息究竟應(yīng)該返回什么,查一下文檔會(huì)更穩(wěn)妥一些。

    這才算是功德圓滿了。

    4.2.3.??????消息的優(yōu)先級(jí)

    上一節(jié)中其實(shí)已經(jīng)暗示了這一點(diǎn),來(lái)自于其他線程的發(fā)送的消息優(yōu)先級(jí)會(huì)高一點(diǎn)點(diǎn)。

    不過(guò)還需要注意,還有那么幾個(gè)優(yōu)先級(jí)比正常的消息低一點(diǎn)點(diǎn)的。它們是:WM_PAINT、WM_TIMER、WM_QUIT。只有在隊(duì)列中沒(méi)有其他消息的時(shí)候,這幾個(gè)消息才會(huì)被處理,多個(gè) WM_PAINT 消息還會(huì)被合并以提高效率(內(nèi)幕揭示:WM_PAINT 其實(shí)也是一個(gè)標(biāo)志位,所以看上去是被“合并了”)。

    其他所有消息則以先進(jìn)先出(FIFO)的方式被處理。

    4.2.4.??????沒(méi)有處理的消息呢?

    有人會(huì)問(wèn)出這個(gè)問(wèn)題的。事實(shí)上,這差不多就是一個(gè)偽命題,基本不存在沒(méi)有處理的消息。從 4.2.2 節(jié)的敘述也可以看出,消息總會(huì)流到某一個(gè)處理分支里去。

    那么,我本人傾向于提問(wèn)者在問(wèn)這樣一個(gè)問(wèn)題:如果窗口回調(diào)函數(shù)沒(méi)有處理某個(gè)消息,那這個(gè)消息最終怎么樣了?其實(shí)這還是取決于回調(diào)函數(shù)實(shí)現(xiàn)者的意志。如果你只是簡(jiǎn)單地返回,那事實(shí)上也是進(jìn)行了處理,只不過(guò),處理的方式是“什么都沒(méi)做”而已;如果你把消息傳遞給 DefWindowProc,那么它會(huì)處理自己感興趣的若干消息,對(duì)于別的消息,它也一概不管,直接返回。

    4.3.???????消息死鎖

    假設(shè)有線程A和B, 現(xiàn)在有以下步驟:

    1) 線程A SendMessage 給線程B,A 等待消息在線程B 中處理后返回

    2) 線程 B 收到了線程A 發(fā)來(lái)的消息,并進(jìn)行處理,在處理過(guò)程中,B 也向線程 A SendMessage,然后等待從A 返回。

    此時(shí)線程A正等待從線程B返回,無(wú)法處理B發(fā)來(lái)的消息,從而導(dǎo)致了線程A 和B相互等待,形成死鎖。

    以此類(lèi)推,多個(gè)線程也可以形成環(huán)形死鎖。

    可以使用 SendNotifyMessage 或 SendMessageTimeout來(lái)避免出現(xiàn)此類(lèi)死鎖。

    (作者注:對(duì)兩個(gè)線程互相 SendMessage 曾經(jīng)專(zhuān)門(mén)寫(xiě)程序進(jìn)行過(guò)驗(yàn)證,結(jié)果卻沒(méi)有死鎖,不知道是不是新一些的 Windows 系統(tǒng)作了特殊的處理。請(qǐng)大家自行驗(yàn)證。)

    4.4.???????模態(tài)(Modal)

    這個(gè)詞匯曾給我?guī)?lái)極大的困惑,我曾經(jīng)做過(guò)不少的努力,想弄清楚為什么當(dāng)初系統(tǒng)的構(gòu)建者使用“模態(tài)”這個(gè)詞匯來(lái)表達(dá)這樣一種情景,但是最后失敗了。我不得不接受這個(gè)詞,并運(yùn)用它。直到數(shù)天前,我找到了一個(gè)對(duì)模態(tài)的簡(jiǎn)要介紹,如果有興趣,各位可以自己去看:http://www.usabilityfirst.com/glossary/main.cgi?function=display_term&term_id=320。(我曾做過(guò)的另外一個(gè)努力是想知道為什么Handle會(huì)被翻譯為“句柄”,或者,是誰(shuí)首先這樣翻譯的,迄今無(wú)解)。Windows 中的模態(tài)有好幾個(gè)場(chǎng)景,比較典型的有:

    顯示了一個(gè)對(duì)話框

    顯示出一個(gè)菜單

    操作滾動(dòng)條

    移動(dòng)窗口

    改變窗口大小

    把我的體會(huì)歸納起來(lái),那就是:如果進(jìn)入了一個(gè)模態(tài)場(chǎng)景,那么,除了這個(gè)模態(tài)本身的明確目標(biāo),其余操作被一概禁止。概念上可以理解為,模態(tài),是一種獨(dú)占模式、一種強(qiáng)制模式,一種霸道模式。

    在 Windows 里,模態(tài)的實(shí)現(xiàn)其實(shí)很簡(jiǎn)單,只不過(guò)就是包含了自己的消息循環(huán)而已,說(shuō)穿了毫無(wú)懸念可言,但是如果不明白這個(gè)內(nèi)幕的話,就會(huì)覺(jué)得很神秘。那么,根據(jù)此結(jié)論,我們就可以做一些有趣(或者有意義)的事情了,看一下以下代碼,預(yù)測(cè)一下 TestModal 的執(zhí)行結(jié)果:

    [cpp]??view plain?copy

  • void?CALLBACK?RequestQuit(HWNDhwnd,?UINT?uMsg,?UINT?idEvent,?DWORD?dwTime);??
  • void?TestModal()??
  • {??
  • ???????????????UINT?uTimerId?=SetTimer(NULL,?66,?1000,?RequestQuit);??
  • ???????????????MessageBox(NULL,?NULL,?NULL,MB_OK);??
  • ???????????????KillTimer(NULL,?uTimerId);??
  • }??
  • ??
  • void?CALLBACK?RequestQuit(HWND?hwnd,?UINT?uMsg,?UINT?idEvent,?DWORD?dwTime)??
  • {??
  • ???????????????PostMessage(NULL,?WM_QUIT,0,?0);??
  • }??
  • void CALLBACK RequestQuit(HWNDhwnd, UINT uMsg, UINT idEvent, DWORD dwTime);

  • void TestModal()

  • {

  • UINT uTimerId =SetTimer(NULL, 66, 1000, RequestQuit);

  • MessageBox(NULL, NULL, NULL,MB_OK);

  • KillTimer(NULL, uTimerId);

  • }

  • void CALLBACK RequestQuit(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)

  • {

  • PostMessage(NULL, WM_QUIT,0, 0);

  • }

  • 答案見(jiàn)本大節(jié)末尾。

    需要提醒的是,模態(tài)是用戶(hù)界面里相當(dāng)重要而普遍的一個(gè)概念,不僅存在于 Windows 環(huán)境下,也存在于其他的用戶(hù)界面系統(tǒng)中,例如 Symbian。

    4.5.???????與消息處理有關(guān)的鉤子(Hook)

    很多人都或多或少地聽(tīng)說(shuō)過(guò)或者接觸過(guò)鉤子。鉤子在處理事務(wù)的正常流程之外,額外給予了我們一種監(jiān)聽(tīng)或者控制的方式(注意:在 Windows Mobile 系統(tǒng)下,鉤子并不被正式支持)。

    (TODO: 細(xì)化,不過(guò)由于這個(gè)內(nèi)容針對(duì)桌面系統(tǒng)更多,所以暫時(shí)可以略過(guò))

    4.6.???????所謂的反射(Reflection)

    上文也已經(jīng)提到,反射通常會(huì)在對(duì) Windows API 的封裝類(lèi)或者類(lèi)庫(kù)中出現(xiàn),這是由于Windows SDK 的 API 是以 C 的風(fēng)格暴露給使用者的,與 C++ 語(yǔ)言的主要用類(lèi)編程的風(fēng)格有一些需要嚙合的地方。

    舉例來(lái)說(shuō),一個(gè)Button,在 SDK 中是一個(gè)已經(jīng)定型的控件,基本上實(shí)現(xiàn)了自包容,要擴(kuò)展它的功能的話(例如,繪制不同的外觀),系統(tǒng)把接口(廣義上的接口,即一種交互上的契約)制定為發(fā)給 Button 的屬主(通常就是父窗口)的兩條消息(WM_MEASUREITEM 和 WM_DRAWITEM)。其道理在于,使用 Button 控件的父窗口,往往是用戶(hù)自己實(shí)現(xiàn)的,處理起來(lái)更方便,而不需要對(duì) Button 自身做什么手腳。

    但是,這種交互方式在 C++ 的世界里是相當(dāng)忌諱的。C++ 的自包容單位是對(duì)象,那么一個(gè) Button 對(duì)象的封裝類(lèi),假定是CButton,不能自己處理自己的繪制問(wèn)題,這是不太符合法則的(盡管不是不可以)。

    為了消除這一不和諧音,就有人提出了反射機(jī)制。其核心就在于,對(duì)于本該子控件自己處理的事件所對(duì)應(yīng)的消息(如前面的 WM_DRAWITEM),父窗口即使收到,也不進(jìn)行直接處理,而是把這個(gè)消息重新發(fā)回給子控件本身。

    這樣帶來(lái)一個(gè)問(wèn)題,當(dāng) Button 收到一個(gè) WM_DRAWITEM消息時(shí),弄不清楚究竟是自己的子窗口發(fā)來(lái)的(雖說(shuō)往 Button 上建立子窗口不常見(jiàn),但不是不可以),還是父窗口把原本是自己的消息反射回來(lái)了。所以,最后微軟給出一個(gè)解決辦法,就是反射消息的時(shí)候,把消息的值上加一個(gè)固定的附加值,這個(gè)值就是 OCM__BASE。盡管最初只是微軟自己在這樣做,這個(gè)值也完全可以各取各的,但是后來(lái)別的類(lèi)/類(lèi)庫(kù)的編制者幾乎都無(wú)一例外地和微軟保持了一致。

    當(dāng)控件收到消息之后,先把這個(gè)附加值減掉,就可以知道是哪一條消息被反射回來(lái)了,然后再作相應(yīng)的處理。

    4.4節(jié)小測(cè)試的答案:一個(gè)消息框顯示大概 1 秒鐘的時(shí)間,然后自動(dòng)消失。有的人根據(jù)這一表現(xiàn),寫(xiě)出了自己的超時(shí)候自動(dòng)關(guān)閉的消息框。如果各位有興趣,可以自己嘗試也實(shí)現(xiàn)一下。(提示:需要考慮一下用戶(hù)先于定時(shí)器觸發(fā)就手動(dòng)關(guān)閉了消息框的情況)

    5.???????Windows 的消息本質(zhì)

    一個(gè)特殊的事件同步機(jī)制,使用多種常規(guī)線程間同步機(jī)制實(shí)現(xiàn)。

    6.???????Windows 的消息操縱

    注意:以下討論中用淺綠色標(biāo)注的函數(shù),表示在 WindowsMobile 平臺(tái)上是沒(méi)有的。

    [cpp]??view plain?copy

  • SendMessage??
  • ??
  • PostMessage??
  • SendMessage

  • PostMessage

  • 在使用消息的過(guò)程中,這兩個(gè)函數(shù)的使用率是最高的。初學(xué)者有時(shí)會(huì)搞不清楚這兩個(gè)發(fā)送消息的函數(shù)的使用場(chǎng)景,容易誤用。所以放在這里一起說(shuō)。其實(shí)上面已經(jīng)對(duì) SendMessage 做了很多的介紹,所以在這兒的重點(diǎn)會(huì)放在 PostMessage 上。相較 SendMessage而言,PostMessage 的工作要輕松許多,只要找到知道那個(gè)的窗口句柄所在的線程,把消息放到該線程的消息隊(duì)列里就可以了,完全不理會(huì)這條消息最終的命運(yùn),是不是被正確處理了。

    這一點(diǎn),從 PostMessage 和 SendMessage 的返回值的不同也有體現(xiàn)。PostMessage 函數(shù)的返回值是 BOOL 類(lèi)型,體現(xiàn)的是投遞操作是否成功。投遞操作是有可能失敗的,盡管我們不愿意同時(shí)也確實(shí)很少看到。例如,目標(biāo)線程的消息隊(duì)列已經(jīng)滿(在 16 位時(shí)代出現(xiàn)概率較高),或者更糟糕,目標(biāo)線程根本就沒(méi)有消息隊(duì)列。

    當(dāng)然,PostMessage 也要檢查窗口句柄的合法性,不過(guò)和SendMessage 不同的一點(diǎn)是,它允許窗口句柄是 NULL。在此情況下,對(duì)它的調(diào)用就等價(jià)于調(diào)用 PostThreadMessage 向自身所在線程投遞一條消息。

    從上面的描述可以很容易地看出,PostMessage 和 SendMessage 的本質(zhì)區(qū)別在于前者發(fā)出的消息是異步處理的,而后者發(fā)出的消息是同步處理的。理解這一點(diǎn)非常重要。

    從上面的這個(gè)結(jié)果推演,還可以得到另外一個(gè)有時(shí)會(huì)很有用的推論。在本線程之內(nèi),如果你在處理某個(gè)窗口消息的時(shí)候,希望在處理之后開(kāi)展另一項(xiàng)以此消息為前提的工作,那么可以向本窗口 Post 一條消息,來(lái)作為該后續(xù)工作的觸發(fā)機(jī)制。

    ?

    [cpp]??view plain?copy

  • GetMessage??
  • GetMessage

    檢查線程的消息隊(duì)列,如果有消息就取出該消息到一個(gè)傳入的 MSG 結(jié)構(gòu)中并返回,沒(méi)有消息,就等待。等待時(shí)線程處于休眠狀態(tài),CPU被分配給系統(tǒng)內(nèi)的其他線程使用。

    需要注意的是,由其它線程 Send 過(guò)來(lái)的消息,會(huì)在這里就地處理(即調(diào)用相應(yīng)的窗口回調(diào)函數(shù)),而不會(huì)返回給調(diào)用者。

    [cpp]??view plain?copy

  • DispatchMessage??
  • DispatchMessage

    這個(gè)消息的來(lái)龍去脈在上文中已經(jīng)有較為詳細(xì)的敘述,故此略去。

    [cpp]??view plain?copy

  • TranslateMessage(<SPAN?style="COLOR:?#33ff33">TranslateAccelerator</SPAN>)??
  • TranslateMessage(TranslateAccelerator)

    這個(gè)函數(shù)在本質(zhì)上與消息機(jī)制的關(guān)系不大,絕大多數(shù)的消息循環(huán)中都出現(xiàn)它的身影是因?yàn)榻^大多數(shù)的程序員都不知道這個(gè)函數(shù)真正是干什么的,僅僅是出于慣例或者初學(xué)時(shí)教科書(shū)上給出的范例。這個(gè)函數(shù)的作用主要和輸入有關(guān),它會(huì)把 WM_KEYDOWN 和 WM_KEYUP 這樣的消息恰當(dāng)?shù)亍⑦m時(shí)地翻譯出新的消息來(lái),如 WM_CHAR。如果你確信某個(gè)線程根本不會(huì)有用戶(hù)輸入方面的需求,基本上可以安全地將之從循環(huán)中移除。

    可以和它相提并論的就是列出的 TranslateAccelerator 函數(shù),這個(gè)函數(shù)會(huì)把用戶(hù)輸入根據(jù)指定的加速鍵(Accelerator)表翻譯為適當(dāng)?shù)拿钕ⅰ?/p>

    [cpp]??view plain?copy

  • PeekMessage??
  • PeekMessage

    窺探線程的消息隊(duì)列。無(wú)論隊(duì)列中有沒(méi)有消息,這個(gè)函數(shù)都立即返回。它的參數(shù)列表與 GetMessage 基本一致,只是多了一個(gè)標(biāo)志參數(shù)。這個(gè)標(biāo)志參數(shù)指定了如果隊(duì)列中如果有消息的話,PeekMessage 的行為。如果該標(biāo)志中含有PM_REMOVE,則 PeekMessage 會(huì)把新消息返回到 MSG 結(jié)構(gòu)中,正如 GetMessage 的行為那樣。如果標(biāo)志中指定了 PM_NOREMOVE,則不會(huì)取出任何消息。

    [cpp]??view plain?copy

  • <SPAN?style="COLOR:?#33ff33">WaitMessage</SPAN>??
  • WaitMessage

    這個(gè)函數(shù)的作用是等待一條消息的到來(lái)。等待期間線程處于休眠狀態(tài),一旦有新消息到來(lái),則立即返回。

    了解了 PeekMessage 和 WaitMessage 之后,理論上,我們可以寫(xiě)出自己的 GetMessage 了。

    [cpp]??view plain?copy

  • SendNotifyMessage??
  • SendNotifyMessage

    這個(gè)函數(shù)很有意思,它的行為屬于看人下菜碟型。如果目標(biāo)線程就是自身所處線程,那么它就是SendMessage;而一旦發(fā)現(xiàn)目標(biāo)線程是其他線程,那它就類(lèi)似于PostMessage,不等待目標(biāo)窗口處理完成。不過(guò),僅僅是類(lèi)似,因?yàn)樗l(fā)出的消息仍然會(huì)被目標(biāo)線程認(rèn)為是 Send 過(guò)來(lái)的。

    SendMessageTimeout

    這個(gè)函數(shù)可以說(shuō)是 SendMessage 函數(shù)家族(相對(duì)PostMessage 而言)之中最強(qiáng)大的函數(shù)。它在標(biāo)準(zhǔn)的SendMessage 函數(shù)的功能前提下,加入了許多額外的控制選項(xiàng)以及一個(gè)超時(shí)設(shè)定。例如,它可以指定,如果發(fā)現(xiàn)目標(biāo)窗口已經(jīng)失去響應(yīng)的話,那么就立即返回;也可以指定如果目標(biāo)窗口的響應(yīng)時(shí)間超過(guò)了指定的超時(shí)時(shí)限的話也返回,而不是無(wú)限等待下去。而且我們知道,SendMessage 是會(huì)固執(zhí)地等待下去的。(內(nèi)幕揭示:SendMessage 其實(shí)就是對(duì) SendMessageTimeout的一個(gè)淺封裝)

    [cpp]??view plain?copy

  • <SPAN?style="COLOR:?#33ff33">SendMessageCallback</SPAN>??
  • SendMessageCallback

    與 SendMessageTimeout 不同,這個(gè)函數(shù)在另外一個(gè)方向上對(duì)標(biāo)準(zhǔn)的 SendMessage 進(jìn)行了擴(kuò)展。它的行為與SendNotifyMessage 類(lèi)似,只不過(guò)允許在對(duì)方處理完消息之后,指定一個(gè)本線程內(nèi)的后續(xù)處理函數(shù)。仔細(xì)觀察可以發(fā)現(xiàn),SendNotifyMessage 其實(shí)是本函數(shù)的一個(gè)特例。

    對(duì)這個(gè)函數(shù)的使用場(chǎng)景較少,實(shí)際上,作者幾乎從來(lái)沒(méi)有見(jiàn)到必須使用它的情況。網(wǎng)上有一些對(duì)此函數(shù)的討論和測(cè)試代碼,但很少有實(shí)用價(jià)值。(恐怕這也是 Windows Mobile 沒(méi)有實(shí)現(xiàn)此函數(shù)的原因之一。)

    [cpp]??view plain?copy

  • PostQuitMessage??
  • PostQuitMessage

    這個(gè)函數(shù)的名字具有迷惑性。事實(shí)上,它本身并不會(huì)投遞任何消息,而是偷偷在系統(tǒng)內(nèi)部置了一個(gè)標(biāo)志,當(dāng)調(diào)用 GetMessage 時(shí)會(huì)檢測(cè)此標(biāo)志位。若此標(biāo)志位被置位,而且隊(duì)列中已經(jīng)沒(méi)有別的符合條件的投遞消息,則 GetMessage 返回 FALSE,用以終止消息循環(huán)。

    不過(guò),有人會(huì)有這樣的疑惑。我們知道,PostMessage 當(dāng)窗口句柄為 NULL 的時(shí)候,就相當(dāng)于 PostThreadMessage(GetCurrentThreadId(), …),那么,為什么不用 PostMessage(NULL, WM_QUIT, 0, 0),而要引入這么一個(gè)單獨(dú)的 API 呢?有的人給出的原因是,這個(gè) API 出現(xiàn)在 Windows 的 16 位時(shí)代,當(dāng)時(shí)還沒(méi)有線程的概念。這個(gè)答案仔細(xì)推敲的話,其實(shí)似是而非,因?yàn)橥耆梢园堰M(jìn)程的執(zhí)行看作是一個(gè)線程。真正的原因,可能從前文能得到一些思考線索,尤其注意“隊(duì)列中已經(jīng)沒(méi)有別的符合條件的投遞消息”這個(gè)敘述。

    [cpp]??view plain?copy

  • PostThreadMessage??
  • PostThreadMessage

    跨線程投遞消息。我們知道,消息隊(duì)列是屬于線程的,所以,可以不指定目標(biāo)窗口而只指定目標(biāo)線程就投遞消息。投遞到目標(biāo)線程的消息通常會(huì)被 GetMessage取出,但是,由于沒(méi)有指定目標(biāo)窗口,所以不會(huì)被派發(fā)到任何一個(gè)窗口回調(diào)函數(shù)中。

    請(qǐng)注意上文中的通常二字。這是因?yàn)樵谝话愕那闆r下,我們是按照 GetMessage(&msg, NULL, 0, 0) 這樣的形式對(duì) GetMessage 進(jìn)行調(diào)用的,但是,第二個(gè)參數(shù)是一個(gè)窗口句柄,如果指定了一個(gè)合法的窗口句柄,那么 GetMessage 就只會(huì)取出與該窗口有關(guān)的投遞消息。如果這樣的調(diào)用放在線程的主消息循環(huán)中,就可能會(huì)造成消息積壓(這和你在本線程中究竟創(chuàng)建了多少個(gè)窗口有關(guān))。所幸的是,迄今我還沒(méi)有見(jiàn)到過(guò)有誰(shuí)這樣使用 GetMessage。

    [cpp]??view plain?copy

  • <SPAN?style="COLOR:?#33ff33">BroadcastSystemMessage[Ex]</SPAN>??
  • BroadcastSystemMessage[Ex]

    我們一般所接觸到的消息都是發(fā)送給窗口的,其實(shí), 消息的接收者可以是多種多樣的,它可以是應(yīng)用程序(application)、可安裝驅(qū)動(dòng)程序(installable driver)、網(wǎng)絡(luò)驅(qū)動(dòng)程序(networkdriver)、系統(tǒng)級(jí)設(shè)備驅(qū)動(dòng)程序(system-leveldevice driver)等,用 BroadcastSystemMessage這個(gè)API可以對(duì)以上系統(tǒng)組件發(fā)送消息。

    [cpp]??view plain?copy

  • InSendMessage<SPAN?style="COLOR:?#33ff33">[Ex]</SPAN>??
  • InSendMessage[Ex]

    這個(gè)函數(shù)用于在處理某條消息時(shí),檢查消息是不是來(lái)自于其他線程的發(fā)送操作。它的使用場(chǎng)景也極其有限,除非你確實(shí)計(jì)劃限制某些消息的來(lái)源和產(chǎn)生方式。

    [cpp]??view plain?copy

  • <SPAN?style="COLOR:?#33ff33">ReplyMessage</SPAN>??
  • ReplyMessage

    這個(gè)函數(shù)在 MSDN 中的解釋非常簡(jiǎn)單,只有寥寥數(shù)語(yǔ),幾乎到了模糊不清的地步。從示例代碼段來(lái)推測(cè),其作用大概是:消息的接收線程(目標(biāo)線程)在處理過(guò)程中可以通過(guò)調(diào)用此函數(shù)使得消息的發(fā)送線程(源線程)結(jié)束等待狀態(tài)繼續(xù)執(zhí)行。

    根據(jù)微軟的文檔,其官方建議是:在處理每個(gè)有可能來(lái)自于其他線程的消息的時(shí)候,如果某一步驟的處理會(huì)調(diào)用到導(dǎo)致線程移交控制的函數(shù)(原文如此:any function that causes the thread to yield control),都應(yīng)該先調(diào)用InSendMessage 類(lèi)屬的函數(shù)進(jìn)行判斷,如果返回TRUE,則要立即使用 ReplyMessage 答復(fù)消息的源線程。

    “會(huì)導(dǎo)致線程移交控制的函數(shù)”,MSDN 給出的例子是 DialogBox,這使得我做出自己的推測(cè),這樣的函數(shù),至少包括會(huì)導(dǎo)致進(jìn)入某種模態(tài)場(chǎng)景的函數(shù)。

    至于“有可能來(lái)自于其他線程的消息”,在 Windows 世界里的現(xiàn)實(shí)狀況是,幾乎任何一個(gè)消息都會(huì)來(lái)自于其他線程。

    我多年以來(lái)的觀察可以斷定,現(xiàn)實(shí)中有無(wú)數(shù)沒(méi)有進(jìn)行以上流程判斷的代碼都在運(yùn)行,而且也幾乎沒(méi)有暴露出什么嚴(yán)重的不良后果。這使得我有理由猜測(cè),微軟也許已經(jīng)把對(duì)此情況的處理隱含到了系統(tǒng)內(nèi)部。更何況,Windows Mobile 中根本就沒(méi)有ReplyMessage 這個(gè) API。

    [cpp]??view plain?copy

  • GetMessagePos??
  • ??
  • <SPAN?style="COLOR:?#33ff33">GetMessageTime</SPAN>??
  • GetMessagePos

  • GetMessageTime

  • 這兩個(gè)函數(shù)用于訪問(wèn)當(dāng)前處理的消息的另外兩個(gè)信息,對(duì)應(yīng)于 MSG 結(jié)構(gòu)里的相應(yīng)域。它們存在的原因是因?yàn)榇翱诨卣{(diào)/消息處理函數(shù)一般都不會(huì)傳遞這兩個(gè)數(shù)據(jù)。

    [cpp]??view plain?copy

  • MsgWaitForMultipleObjects[Ex]??
  • MsgWaitForMultipleObjects[Ex]

    這是一個(gè)在講到消息相關(guān)的內(nèi)容時(shí),十有八九會(huì)被人遺忘的 API。它屬于傳統(tǒng)的 ITC、IPC 和 Windows 特有的消息機(jī)制的交叉地帶。不過(guò),在 Windows 平臺(tái)上,如果還沒(méi)有了解并掌握這個(gè)函數(shù),那一定不能稱(chēng)其為專(zhuān)家。

    這個(gè)函數(shù)揭示了以下平時(shí)不太為人所注意的細(xì)節(jié):

    1、? 消息和內(nèi)核對(duì)象,有千絲萬(wàn)縷的聯(lián)系

    2、? 消息和內(nèi)核對(duì)象可以按照相似的方式去處理

    如果說(shuō),SendMessageTimeout 是 Windows 平臺(tái)下最強(qiáng)大的發(fā)送消息的機(jī)制,那么,MsgWaitForMultipleObjects[Ex] 就是最強(qiáng)大等待機(jī)制,它是 WaitMessage 和 WaitFor… 函數(shù)族的集大成者。根據(jù)我們上面使用 WaitMessage 和 PeekMessage 結(jié)合使用可以取代 GetMessage 的論斷,我們也可以這樣說(shuō),MsgWaitForMultipleObjects[Ex]是最強(qiáng)大的消息循環(huán)發(fā)動(dòng)機(jī)。

    仔細(xì)描述此函數(shù)會(huì)超出單純的消息機(jī)制范疇,所以把深入學(xué)習(xí)它的工作遺留給各位自己去實(shí)踐。

    7.???????Windows 的消息辨析

    7.1.???????SendMessage和PostMessage的區(qū)別

    請(qǐng)考慮有面試考官問(wèn)及此問(wèn)題時(shí)你如何組織回答。J

    7.2.???????SendMessage發(fā)送的消息不進(jìn)入消息隊(duì)列嗎

    提示:請(qǐng)考慮跨線程的情況。

    這個(gè)說(shuō)法不完全正確。當(dāng)SendMessage發(fā)送的消息跨越線程邊界時(shí),消息其實(shí)被加入到了目標(biāo)線程的消息隊(duì)列里。不過(guò),在線程隊(duì)列里,別的線程Send過(guò)來(lái)的消息會(huì)被優(yōu)先處理。

    7.3.???????PostMessage(WM_QUIT)和PostQuitMessage()的區(qū)別,可能會(huì)產(chǎn)生怎樣的差異化執(zhí)行效果

    提示:請(qǐng)考慮發(fā)生以上某個(gè)調(diào)用時(shí),消息隊(duì)列里不為空的情況。

    7.4.???????文章開(kāi)頭的經(jīng)典消息循環(huán)正確么?

    提示:請(qǐng)注意 GetMessage 的返回值。

    曾經(jīng)有很長(zhǎng)一段時(shí)間,連微軟的例子也這樣寫(xiě)。但是,這樣寫(xiě)其實(shí)是不對(duì)的。原因很簡(jiǎn)單,GetMessage不僅僅是取道消息返回 TRUE,取不到(遇到WM_QUIT 消息)返回FALSE這么單純,它還會(huì)出錯(cuò)。出錯(cuò)時(shí)返回 -1。這就了能使得經(jīng)典循環(huán)在GetMessage發(fā)生錯(cuò)誤時(shí)變成死循環(huán)。微軟的建議是,當(dāng)GetMessage返回 -1 時(shí),跳出循環(huán),結(jié)束程序。

    注:本文乃是數(shù)年前的培訓(xùn)講義,文中有某處不完整,迄今未補(bǔ),讀者自察之。

    Windows Features

    >桌面窗口

    ? ? GetDesktopWindow:獲取桌面窗口句柄

    ? ? SystemParameter(wAction=SPI_SETDESKWALLPAPER):設(shè)置桌面壁紙

    >窗體結(jié)構(gòu)

    >客戶(hù)區(qū)和非客戶(hù)區(qū)

    ? ? 客戶(hù)區(qū):應(yīng)用程序中可以直接輸出的區(qū)域

    ? ? 非客戶(hù)區(qū):應(yīng)用程序中的特殊區(qū)域(保護(hù)標(biāo)題欄、菜單欄、系統(tǒng)按鈕等)

    >窗口創(chuàng)建消息:在這些消息中處理程序初始化的任務(wù)

    ? ? WM_NCCREATE:創(chuàng)建非客戶(hù)區(qū)消息

    ? ? WM_CREATE:創(chuàng)建客戶(hù)區(qū)消息

    ? ? WM_PARENTNOTIFY:創(chuàng)建子窗口后系統(tǒng)發(fā)送給父窗口

    >?窗口類(lèi)型

    ? ? 1.OverlappedWindows:

    ? ? ? ? 是一種頂層窗口

    ? ? ? ? 有標(biāo)題欄、邊框、客戶(hù)區(qū)(WS_OVERLAPPED),可選:窗口菜單、最小化和最大化按鈕、滾動(dòng)條(WS_OVERLAPPEDWINDOW)

    ? ? ? ? 通常作為程序的主窗口

    ? ? ? ?

    ? ? 2.Pop-UpWindows:彈出窗口

    ? ? ? ? 是一種特殊的Overlapped窗口

    ? ? ? ? 經(jīng)常用來(lái)彈出對(duì)話框、消息窗口和臨時(shí)窗口

    ? ? ? ? 使用WS_POPUP樣式創(chuàng)建窗口

    ? ??

    ? ? 3.Child Windows:子窗口

    ? ? ? ? WS_CHILD樣式

    ? ? ? ? 局限于父窗口的顯示區(qū),必須為其指定一個(gè)父窗口

    ? ? ? ? 通常用來(lái)切分父窗口

    ? ? ? ? 子窗口顯示默認(rèn)是在父窗口的左上角,如果比父窗口大,則進(jìn)行裁剪

    ? ? ? ? 父窗口的消息影響其子窗口:?

    ? ? ? ? ? ? Destroyed:子窗口先銷(xiāo)毀?

    ? ? ? ? ? ? Hidden:子窗口先隱藏

    ? ? ? ? ? ? Moved:隨著父窗口移動(dòng),移動(dòng)后得自繪

    ? ? ? ? ? ? Show:父窗口先顯示

    ? ? ? ? 裁剪:WS_CLIPCHILDREN樣式使父窗口不能在子窗口之上繪制

    ? ? ? ? 和父窗口關(guān)系:

    ? ? ? ? ? ? SetParent:指定一個(gè)父窗口,參數(shù)為NULL時(shí),顯示在桌面上,可以自由拖動(dòng)

    ? ? ? ? ? ? GetParent:獲得父窗口的句柄

    ? ? ? ? ? ? IsChild:判斷一個(gè)窗口是不是另一個(gè)窗口的子窗口

    ? ? ? ? ? ? EnumChildWindow:枚舉子窗口

    ? ? ? ? ? ? 子窗口和父窗口的類(lèi)型可以不一樣

    ? ? 消息:

    ? ? ? ? ? ? 子窗口的消息直接發(fā)送給子窗口,如被禁止,則發(fā)送到父窗口

    ? ? ? ? ? ? EnableWindow:禁止或啟用一個(gè)子窗口

    ? ? ? ? ? ??

    ? ? ?層疊窗口:WS_EX_LAYERED

    ? ? 消息窗口:

    ? ? ? ? 不可見(jiàn)、沒(méi)有z自序,不能被枚舉,不能收到廣播消息

    ? ? ? ? 使用HWND_MESSAGE句柄

    ? ? ? ? 使用FindWindowEx并傳入HWND_MEAAGE查找窗口

    ? ? ? ? ? ??

    ??窗口關(guān)系

    >前臺(tái)窗口與后臺(tái)窗口

    ? ? 和用戶(hù)交互的窗口叫做前臺(tái)窗口,創(chuàng)建的線程叫做前臺(tái)線程

    ? ? 其他線程創(chuàng)建的窗口叫做后臺(tái)窗口,其他線程叫做后臺(tái)線程

    ? ? 前臺(tái)窗口線程的優(yōu)先級(jí)(9)高于其他線程的(7)

    ? ? 設(shè)置前臺(tái)窗口:點(diǎn)擊、Alt+Tab、Alt+Esc

    ? ? 獲取前臺(tái)窗口句柄:GetForegroundWindow()

    ? ? 設(shè)置前臺(tái)窗口:SetForegroundWindow(),設(shè)置的進(jìn)程需要滿足特定的條件

    >Owned窗口

    ? ? 窗口在其擁有者窗口之上(Z order)

    ? ? 擁有者窗口銷(xiāo)毀時(shí)被擁有者窗口也自動(dòng)銷(xiāo)毀

    ? ? 擁有者窗口最小化時(shí)被擁有者窗口隱藏

    ? ? 只有Overlapped和popup窗口可以稱(chēng)為擁有者窗口,子窗口不能稱(chēng)為擁有者窗口

    ? ? 通過(guò)GetWindow及參數(shù)GW_OWNER獲取窗口的擁有者窗口

    >Z-Order

    ? ? 頂層窗口在所有其它非頂層窗口之上

    ? ? BringWindowToTop、SetWindowPos、DeferWindowPos設(shè)置窗口在Z序的位置

    窗口狀態(tài)

    >Aactive Windows:當(dāng)前用戶(hù)正在使用的窗口

    ? ? SetActiveWindows:切換激活窗口(本進(jìn)程內(nèi)?)

    ? ? SetWindowsPos、DeferWindowPos、SetWindowPlacement

    ? ? GetActiveWindow

    ? ? WM_ACTIVATEAPP:發(fā)送激活窗口切換時(shí)系統(tǒng)發(fā)送給兩個(gè)窗口

    >Disable Windows:

    ? ? Disable窗口不能收到鼠標(biāo)和鍵盤(pán)消息

    ? ? EnableWindow:禁止或啟用一個(gè)窗口

    ? ? IsWindowEnabled:判斷一個(gè)窗口是否可用

    ? ? 子窗口被disable的時(shí)候,父窗口可以接收子窗口的鼠標(biāo)消息

    >可見(jiàn)性

    ? ? 如果一個(gè)窗口被隱藏等同于失效

    ? ? IsWindowVisible:判斷一個(gè)窗口是否可見(jiàn)

    >CloseWindow:最小化窗口

    ? ? SetWindowPlacement與ShowWindows功能類(lèi)似,還可以改變最小化、最大化和恢復(fù)時(shí)默認(rèn)的位置

    ? ? WM_QUERYOPEN:最大化或恢復(fù)窗口時(shí),系統(tǒng)給窗口發(fā)送這個(gè)命令

    ? ? WM_GETMINMAXINOF:后去窗口的默認(rèn)大小信息

    Windows大小和位置

    >WM_GETMINMAXINFO:獲取窗口大小拖動(dòng)時(shí)最大尺寸和最小尺寸

    ? ? WS_THICKFREAME:容許一個(gè)窗口大小被拖動(dòng)

    >WM_SYSCOMMAND:用戶(hù)點(diǎn)擊窗口菜單時(shí)由系統(tǒng)發(fā)送

    ? ? SC_CLOSE:關(guān)閉窗口,發(fā)送WM_CLOSE消息到窗口

    ? ? SC_MAXIMIZE:最大化窗口

    ? ? SC_MINIMIZE:最小化窗口

    ? ? SC_MOVE:移動(dòng)窗口

    ? ? SC_RESTORE:恢復(fù)最大化或最小化之前的大小

    ? ? SC_SIZE:改變窗口大小

    >改變大小或位置:

    ? ? SetWindowPlacement:設(shè)置窗口最大化、最小化,恢復(fù)的大小和位置,以及顯示狀態(tài)

    ? ? MoveWindow和SetWindowPos:設(shè)置窗口位置

    ? ? XXXDeferWindowPos:同時(shí)設(shè)置窗口的大小、位置,Z序和顯示狀態(tài)

    ? ? GetWindowRect:獲取窗口矩形信息,都是相對(duì)于桌面的位置

    ? ? ScreenToClient和MapWindowPoints:進(jìn)行坐標(biāo)轉(zhuǎn)化

    ? ? GetClientRect:獲取客戶(hù)區(qū)的矩形,相對(duì)與自身的

    ? ? TileWindows和?CascadeWindows: 將左右最大化窗口還原? ??

    ? ? ??WM_WINDOWPOSCHANGING:窗口大小和位置改變之后會(huì)收到此消息

    ? ? WM_NCCALSIZE:窗口創(chuàng)建時(shí)和大小改變時(shí)會(huì)收到此消息

    >AnimateWindow:特殊顯示或隱藏窗口

    窗口銷(xiāo)毀

    >DestroyWindow:銷(xiāo)毀窗口

    ? ?給自己和所有子窗口發(fā)送WM_DESTROY消息

    >WM_CLOSE:在銷(xiāo)毀窗口前,提供給用戶(hù)一個(gè)確認(rèn)的機(jī)會(huì)

    >WM_DESTROY:給應(yīng)用程序一次清理資源的機(jī)會(huì)

    >PostQuitMessage:應(yīng)用程序清理完資源時(shí)調(diào)用,退出主消息循環(huán)

    >UpdateWindow:發(fā)送WM_PAINT消息

    ?

    總結(jié)

    以上是生活随笔為你收集整理的windows消息机制详解-3的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。