windows消息機制(轉)


1. 引言
Windows 在操作系統平台占有絕對統治地位,基於Windows 的編程和開發越來越廣泛。
Dos 是過程驅動的,而Windows 是事件驅動的[6],這種差別的存在使得很多Dos 程序員不能
習慣Windows 的程序開發。而很多Windows 程序開發人員也只是對消息運行機制一知半解,
想要掌握Windows 編程的核心,必須深刻理解消息機制。事件驅動圍繞着消息的產生與處
理展開,事件驅動是靠消息循環機制來實現的。也可以理解為消息是一種報告有關事件發生
的通知,消息是Windows 操作系統的靈魂,掌握了消息運行機制就掌握了Windows 編程的
神兵利器。本文將首先闡述Windows 的編程原理,繼而對Windows 的消息運行機制進行分
析,並講述對消息的處理。MFC 是一個廣為使用的編程類庫,對Windows 的消息機制進行
了良好的封裝,所以,在第二部分將着重討論MFC 的消息映射,最后結合編程實際,通過
對MFC 消息映射的分析,非常巧妙的加以應用,以幫助解決實際問題

 

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

 

其中hwnd 是窗口的句柄,這個參數將決定由哪個窗口過程函數對消息進行處理;message
是一個消息常量,用來表示消息的類型;wParam 和lParam 都是32 位的附加信息,具體表
示什么內容,要視消息的類型而定;time 是消息發送的時間;pt 是消息發送時鼠標所在的位
置。

 

2.2 Windows 編程原理
Windows 是一消息(Message)驅動式系統,Windows 消息提供了應用程序與應用程序
之間、應用程序與Windows 系統之間進行通訊的手段。應用程序要實現的功能由消息來觸
發,並靠對消息的響應和處理來完成。Windows 系統中有兩種消息隊列,一種是系統消息隊
列,另一種是應用程序消息隊列。計算機的所有輸入設備由 Windows 監控,當一個事件發
生時,Windows 先將輸入的消息放入系統消息隊列中,然后再將輸入的消息拷貝到相應的應
用程序隊列中,應用程序中的消息循環從它的消息隊列中檢索每一個消息並發送給相應的窗
口函數中。一個事件的發生,到達處理它的窗口函數必須經歷上述過程。
所謂消息就是描述事件發生的信息,Windows 程序是事件驅動的,用這一方法編寫程序
避免了死板的操作模式,因為Windows 程序的執行順序將取決於事件的發生順序,具有不
可預知性。Windows 操作系統,計算機硬件,應用程序之間具有如圖1 所示的關系

 

 

箭頭1 說明操作系統能夠操縱輸入輸出設備,例如讓打印機打印;

箭頭2 說明操作系統能夠感知輸入輸出設備的狀態變化,如鼠標單擊,按鍵按下等,這就是操作系統和計算機硬

件之間的交互關系,應用程序開發者並不需要知道他們之間是如何做到的,我們需要了解的
操作系統與應用程序之間如何交互。

箭頭3 是應用程序通知操作系統執行某個具體的操作,
這是通過調用操作系統的API 來實現的;操作系統能夠感知硬件的狀態變化,但是並不決
定如何處理,而是把這種變化轉交給應用程序,由應用程序決定如何處理,

向上的箭頭4說明了這種轉交情況,操作系統通過把每個事件都包裝成一個稱為消息結構體MSG 來實現

這個過程,也就是消息響應,要理解消息響應,首先需要了解消息的概念和表示。

 

2.3 Windows 消息循環
消息循環[1]是Windows 應用程序存在的根本,應用程序通過消息循環獲取各種消息,並
通過相應的窗口過程函數,對消息加以處理;正是這個消息循環使得一個應用程序能夠響應
外部的各種事件,所以消息循環往往是一個Windows 應用程序的核心部分。
Windows 的消息機制如圖2 所示:

 

 

 

Windows 操作系統為每個線程維持一個消息隊列,當事件產生時,操作系統感知這一事
件的發生,並包裝成消息發送到消息隊列,應用程序通過GetMessage()函數取得消息並存於
一個消息結構體中,然后通過一個TranslateMessage()和DispatchMessage()解釋和分發消息,
下面的代碼描述了Windows 的消息循環。
while(GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
TranslateMessage(&msg)對於大多數消息而言不起作用,但是有些消息,比如鍵盤按鍵按
下和彈起(分別對於KeyDown 和KeyUp 消息),卻需要通過它解釋,產生一個WM_CHAR
消息。DispatchMessage(&msg)負責把消息分發到消息結構體中對應的窗口,交由窗口過程
函數處理。GetMessage()在取得WM_QUIT 之前的返回值都為TRUE,也就是說只有獲取到
WM_QUIT 消息才返回FALSE,才能跳出消息循環

 

2.4 消息的處理
取得的消息將交由窗口處理函數進行處理,對於每個窗口類Windows 為我們預備了一個
默認的窗口過程處理函數DefWindowProc(),這樣做的好處是,我們可以着眼於我們感興趣
的消息,把其他不感興趣的消息傳遞給默認窗口過程函數進行處理。每一個窗口類都有一個
窗口過程函數,此函數是一個回調函數(實現方不全調用,由別的模塊進行調用),它是由Windows 操作系統負責調用的,而應用程
序本身不能調用它。以switch 語句開始,對於每條感興趣的消息都以一個case 引出。
LRESULT CALLBACK WndProc
(
HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam
)
{

switch(uMsgId)
{

case WM_TIMER://對WM_TIMER 定時器消息的處理過程
return 0;
case WM_LBUTTONDOWN://對鼠標左鍵單擊消息的處理過程
reurn 0;
. …
default:
return DefWindowProc(hwnd,uMsgId,wParam,lParam);
}
}
對於每條已經處理過的消息都必須返回0,否則消息將不停的重試下去;對於不感興趣
的消息,交給DefWindowProc()函數進行處理,並需要返回其處理值。


3. MFC 的消息映射
MFC 是Windows 下編程的微軟基礎類庫,封裝了大部分Windows API 和Windows 控件
提供了一套消息映射和命令響應機制,方便了應用程序的開發。MFC 只是通過對Windows
消息映射的進行封裝,使得添加消息響應變得更為簡單,但深究起來,與Windows 消息機
制有一樣的底層實現。


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

AfxWindProc,而原來的窗口過程函數被保存在成員變量m_pfnSuper 中。
在MFC 框架下,通過下面的步驟來對消息進行映射[7]。
1 函數AfxWndProc 接收Windows 操作系統發送的消息。
2 函數AfxWndProc 調用函數AfxCallWndProc 進行消息處理,這里一個進步是把對句柄的
操作轉換成對CWnd 對象的操作。
3 函數AfxCallWndProc 調用CWnd 類的方法WindowProc 進行消息處理。
4 WindowProc 調用OnWndMsg 進行正式的消息處理,即把消息派送到相關的方法中去處理。
5 如果OnWndMsg 方法沒有對消息進行處理的話,就調用DefWindowProc 對消息進行處理。
這就是MFC 對消息調用過程的巧妙封裝。


3.2 MFC 消息分類


1 命令消息(WM_COMMAND)
比如菜單項的選擇,工具欄按鈕點擊等發出該消息。所有派生自CCmdTarget 的類都有
能力接收WM_COMMAND 消息。


2 標准消息(WM_XXX)
比如窗口創建,窗口銷毀等。所有派生自CWnd 的類才有資格接收標准消息。


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


4 自定義消息
利用MFC 編程,可以使用自定義消息。使用自定義消息需要遵循一定的步驟[2]並需要
自己編寫消息響應函數

 


4. MFC 消息的靈活運用
在此,我們給出一個示例程序,演示對MFC 消息的靈活運用,通過此例的剖析,將加
深我們對MFC 消息的理解。


4.1 示例功能描述
本示例程序將演示這樣一種效果:
對話框上有一個CTabCtrl 控件,一個CComboBox 控件,兩個按鈕Button1 和Button2。
CTabCtrl 控件有兩個標簽頁Tab1 和Tab2;CComboxBox 有兩個選項:選項1 和選項2;通
過按鈕(Button1 和Button2)單擊,分別發送CTabCtrl 控件的TCN_SELCHANGE 消息和
下拉列表框的CBN_SELCHANGE 消息,在各自的消息響應函數中只是簡單的對控件選項做
切換和給出提示信息。
單擊Button1 將選中標簽頁Tab1 和下拉列表框的選項1,並彈出提示信息;單擊Button2
將選中標簽頁Tab2 和下拉列表框的選項2,並彈出提示信息。


4.2 程序設計思路
TCN_SELCHANGE 消息和CBN_SELCHANGE 消息都屬於通告消息,此消息由子控件

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


4.3 程序實現步驟
啟動VC++6.0,新建基於對話框的應用程序MsgTest.
在對話框上添加1 個CTabCtrl 控件,一個CComboBox 控件,2 個按鈕Button1 和Button2;
給IDC_TAB1 和IDC_COMBO1 分別關聯控件成員變量m_tab1 和m_cb1;為兩按鈕分
別添加按鈕單擊響應函數。
在對話框的OnInitDlg()函數中為CTabCtrl 控件添加兩個標簽頁,Tab1 和Tab2;為
ComboBox 添加選項1 和2;代碼如下:
m_tab1.InsertItem(0,"Tab1");
m_tab1.InsertItem(1,"Tab2");
m_cb1.AddString("選項1");
m_cb1.AddString("選項2");
用ClassWizard 為CTabCtrl 添加消息響應TCN_SELCHANGE,為CComboBox 添加消息
響應CBN_SELCHANGE。在OnSelchangeTab1()函數中添加代碼
int nIndex=m_tab1.GetCurSel();
CString str;
str.Format("%d",nIndex+1);
MessageBox("Tab"+str+" selected!");
在OnSelchangeCombo1()函數中添加代碼:
int nIndex=m_cb1.GetCurSel();
CString str;
str.Format("%d",nIndex+1);

 

MessageBox("ComboBox 選項"+str+" selected!");
在按鈕1 的響應函數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 的響應函數OnButton2()中添加類似代碼,只需要把m_tab1.SetCurSel(0)和
m_cb1.SetCurSel(0)分別改成m_tab1.SetCurSel(1)和m_cb1.SetCurSel(1)。
通過SendMessage() 函數向控件的父窗口也就是對話框窗口發送相應的消息,
TCN_SELCHANGE 是以WM_NOTIFY 消息的形式發送,參數WPARAM 標識發送
TCN_SELCHANGE 消息的控件ID,LPARAM 是一個NMHDR 結構體的指針,此結構體的
成員code 標識發送什么通告消息,此處是TCN_SELCHANGE,hwndFrom 是發送消息的控
件句柄, 程序中用GetDlgItem()->m_hWdn 獲得, idFrom 是發送消息的控件ID 。
CBN_SELCHANGE 以WM_COMMAND 消息的形式發送,同樣的,通過查閱MSDN,可以
對此消息的兩個參數進行賦值,以保證消息的正確發送。
通過上面的5 個步驟,我們的程序就編寫完成了,單擊Button1,可以發現,CTabCtrl
切換到了Tab1 標簽頁,CComboBox 選擇了“選項1”,並彈出消息對話框。由此可見確實引
起了消息響應函數的調用,完成了預定的功能。
通過查閱MSDN,可以得到其他消息的發送和包裝形式,我們可以方便的加以利用,完
成更為復雜的功能,可以說,掌握了Windows 的消息機制,就掌握了Windows 編程的核心。

 


5. 總結
Windows 消息機制是Windows 編程的本質和核心,對Windows 消息機制的理解能提高
我們Windows 程序開發的能力。本文首先闡述Windows 的消息機制,然后講解了MFC 的
消息映射,消息分類,最后通過示例程序,講解如何借助MSDN,靈活運用消息編程,解
決實際問題。本文對Windows 下的程序開發具有一定的參考和借鑒意義。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM