明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

MFC消息響應(yīng)機(jī)制區(qū)分

[摘要]浙江大學(xué)計(jì)算機(jī)系 胡朝暉 陳奇 俞瑞釗 ---- 摘要: ---- MFC是Windows下程序設(shè)計(jì)的最流行的一個(gè)類(lèi)庫(kù),但是該類(lèi)庫(kù)比較龐雜,尤其是它的消息映射機(jī)制,更是涉及到很多低層的東西,我們?cè)谶@里,對(duì)它的整個(gè)消息映射機(jī)制進(jìn)行了系統(tǒng)的分析,可以幫助程序開(kāi)發(fā)人員對(duì)MFC的消息映射機(jī)制有一個(gè)比較透...
浙江大學(xué)計(jì)算機(jī)系 胡朝暉 陳奇 俞瑞釗  

---- 摘要:

---- MFC是Windows下程序設(shè)計(jì)的最流行的一個(gè)類(lèi)庫(kù),但是該類(lèi)庫(kù)比較龐雜,尤其是它的消息映射機(jī)制,更是涉及到很多低層的東西,我們?cè)谶@里,對(duì)它的整個(gè)消息映射機(jī)制進(jìn)行了系統(tǒng)的分析,可以幫助程序開(kāi)發(fā)人員對(duì)MFC的消息映射機(jī)制有一個(gè)比較透徹的了解。

---- 關(guān)鍵詞:面向?qū)ο?消息映射 MFC 程序設(shè)計(jì)

一.引言
---- VC++的MFC類(lèi)庫(kù)實(shí)際上是Windows下C++編程的一套最為流行的類(lèi)庫(kù)。MFC的框架結(jié)構(gòu)大大方便了程序員的編程工作,但是為了更加有效、靈活的使用MFC編程,了解MFC的體系結(jié)構(gòu)往往可以使編程工作事半功倍。它合理的封裝了WIN32 API函數(shù),并設(shè)計(jì)了一套方便的消息映射機(jī)制。但這套機(jī)制本身比較龐大和復(fù)雜,對(duì)它的分析和了解無(wú)疑有助于我們寫(xiě)出更為合理的高效的程序。這里我們簡(jiǎn)單的分析MFC的消息響應(yīng)機(jī)制,以了解MFC是如何對(duì)Windows的消息加以封裝,方便用戶的開(kāi)發(fā)。

二.SDK下的消息機(jī)制實(shí)現(xiàn)
---- 這里簡(jiǎn)單的回顧一下SDK下我們是如何進(jìn)行Windows的程序開(kāi)發(fā)的。一般來(lái)說(shuō),Windows的消息都是和線程相對(duì)應(yīng)的。即Windows會(huì)把消息發(fā)送給和該消息相對(duì)應(yīng)的線程。在SDK的模式下,程序是通過(guò)GetMessage函數(shù)從和某個(gè)線程相對(duì)應(yīng)的消息隊(duì)列里面把消息取出來(lái)并放到一個(gè)特殊的結(jié)構(gòu)里面,一個(gè)消息的結(jié)構(gòu)是一個(gè)如下的STRUCTURE。

typedef struct tagMSG {
                  HWND   hwnd;
                  UINT   message;
                  WPARAM wParam;
                  LPARAM lParam;
                  DWORD  time;
                  POINT  pt;
}MSG;


---- 其中hwnd表示和窗口過(guò)程相關(guān)的窗口的句柄,message表示消息的ID號(hào),wParam和lParam表示和消息相關(guān)的參數(shù),time表示消息發(fā)送的時(shí)間,pt表示消息發(fā)送時(shí)的鼠標(biāo)的位置。

---- 然后TranslateMessage函數(shù)用來(lái)把虛鍵消息翻譯成字符消息并放到響應(yīng)的消息隊(duì)列里面,最后DispatchMessage函數(shù)把消息分發(fā)到相關(guān)的窗口過(guò)程。然后窗口過(guò)程根據(jù)消息的類(lèi)型對(duì)不同的消息進(jìn)行相關(guān)的處理。在SDK編程過(guò)程中,用戶需要在窗口過(guò)程中分析消息的類(lèi)型和跟消息一起的參數(shù)的含義,做不同的處理,相對(duì)比較麻煩,而MFC把消息調(diào)用的過(guò)程給封裝起來(lái),使用戶能夠通過(guò)ClassWizard方便的使用和處理Windows的各種消息。

三.MFC的消息實(shí)現(xiàn)機(jī)制
---- 我們可以看到,在MFC的框架結(jié)構(gòu)下,可以進(jìn)行消息處理的類(lèi)的頭文件里面都會(huì)含有DECLARE_MESSAGE_MAP()宏,這里主要進(jìn)行消息映射和消息處理函數(shù)的聲明?梢赃M(jìn)行消息處理的類(lèi)的實(shí)現(xiàn)文件里一般都含有如下的結(jié)構(gòu)。

BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)
//{{AFX_MSG_MAP(CInheritClass)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()


---- 這里主要進(jìn)行消息映射的實(shí)現(xiàn)和消息處理函數(shù)的實(shí)現(xiàn)。
---- 所有能夠進(jìn)行消息處理的類(lèi)都是基于CCmdTarget類(lèi)的,也就是說(shuō)CCmdTarget類(lèi)是所有可以進(jìn)行消息處理類(lèi)的父類(lèi)。CCmdTarget類(lèi)是MFC處理命令消息的基礎(chǔ)和核心。

---- 同時(shí)MFC定義了下面的兩個(gè)主要結(jié)構(gòu):

AFX_MSGMAP_ENTRY
struct AFX_MSGMAP_ENTRY
{
UINT nMessage;   // windows message
UINT nCode;  // control code or WM_NOTIFY code
UINT nID;    
    // control ID (or 0 for windows messages)
UINT nLastID;   
// used for entries specifying a range of control id's
UINT nSig;       
// signature type (action) or pointer to message #
AFX_PMSG pfn;    // routine to call (or special value)
};
和AFX_MSGMAP
struct AFX_MSGMAP
{
#ifdef _AFXDLL
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
const AFX_MSGMAP* pBaseMap;
#endif
const AFX_MSGMAP_ENTRY* lpEntries;
};

   其中AFX_MSGMAP_ENTRY結(jié)構(gòu)包含了
一個(gè)消息的所有相關(guān)信息,其中

nMessage為Windows消息的ID號(hào)
nCode為控制消息的通知碼
nID為Windows控制消息的ID
nLastID表示如果是一個(gè)指定范圍的消息被映射的話,
nLastID用來(lái)表示它的范圍。
nSig表示消息的動(dòng)作標(biāo)識(shí)
AFX_PMSG pfn 它實(shí)際上是一個(gè)指向
和該消息相應(yīng)的執(zhí)行函數(shù)的指針。

---- 而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í)候,就調(diào)用其基類(lèi)的消息響應(yīng)。

---- 現(xiàn)在我們來(lái)分析MFC是如何讓窗口過(guò)程來(lái)處理消息的,實(shí)際上所有MFC的窗口類(lèi)都通過(guò)鉤子函數(shù)_AfxCbtFilterHook截獲消息,并且在鉤子函數(shù)_AfxCbtFilterHook中把窗口過(guò)程設(shè)定為AfxWndProc。原來(lái)的窗口過(guò)程保存在成員變量m_pfnSuper中。

---- 所以在MFC框架下,一般一個(gè)消息的處理過(guò)程是這樣的。

函數(shù)AfxWndProc接收Windows操作系統(tǒng)發(fā)送的消息。

函數(shù)AfxWndProc調(diào)用函數(shù)AfxCallWndProc進(jìn)行消息處理,這里一個(gè)進(jìn)步是把對(duì)句柄的操作轉(zhuǎn)換成對(duì)CWnd對(duì)象的操作。

函數(shù)AfxCallWndProc調(diào)用CWnd類(lèi)的方法WindowProc進(jìn)行消息處理。注意AfxWndProc和AfxCallWndProc都是AFX的API函數(shù)。而WindowProc已經(jīng)是CWnd的一個(gè)方法。所以可以注意到在WindowProc中已經(jīng)沒(méi)有關(guān)于句柄或者是CWnd的參數(shù)了。

方法WindowProc調(diào)用方法OnWndMsg進(jìn)行正式的消息處理,即把消息派送到相關(guān)的方法中去處理。消息是如何派送的呢?實(shí)際上在CWnd類(lèi)中都保存了一個(gè)AFX_MSGMAP的結(jié)構(gòu),而在AFX_MSGMAP結(jié)構(gòu)中保存有所有我們用ClassWizard生成的消息的數(shù)組的入口,我們把傳給OnWndMsg的message和數(shù)組中的所有的message進(jìn)行比較,找到匹配的那一個(gè)消息。實(shí)際上系統(tǒng)是通過(guò)函數(shù)AfxFindMessageEntry來(lái)實(shí)現(xiàn)的。找到了那個(gè)message,實(shí)際上我們就得到一個(gè)AFX_MSGMAP_ENTRY結(jié)構(gòu),而我們?cè)谏厦嬉呀?jīng)提到AFX_MSGMAP_ENTRY保存了和該消息相關(guān)的所有信息,其中主要的是消息的動(dòng)作標(biāo)識(shí)和跟消息相關(guān)的執(zhí)行函數(shù)。然后我們就可以根據(jù)消息的動(dòng)作標(biāo)識(shí)調(diào)用相關(guān)的執(zhí)行函數(shù),而這個(gè)執(zhí)行函數(shù)實(shí)際上就是通過(guò)ClassWizard在類(lèi)實(shí)現(xiàn)中定義的一個(gè)方法。這樣就把消息的處理轉(zhuǎn)化到類(lèi)中的一個(gè)方法的實(shí)現(xiàn)上。舉一個(gè)簡(jiǎn)單的例子,比如在View中對(duì)WM_LButtonDown消息的處理就轉(zhuǎn)化成對(duì)如下一個(gè)方法的操作。
   void CInheritView::OnLButtonDown
(UINT nFlags, CPoint point)
   {
// TODO: Add your message
handler code here and/or call default
CView::OnLButtonDown(nFlags, point);
   }


注意這里CView::OnLButtonDown(nFlags, point)實(shí)際上就是調(diào)用CWnd的Default()方法。 而Default()方法所做的工作就是調(diào)用DefWindowProc對(duì)消息進(jìn)行處理。這實(shí)際上是調(diào)用原來(lái)的窗口過(guò)程進(jìn)行缺省的消息處理。

如果OnWndMsg方法沒(méi)有對(duì)消息進(jìn)行處理的話,就調(diào)用DefWindowProc對(duì)消息進(jìn)行處理。這是實(shí)際上是調(diào)用原來(lái)的窗口過(guò)程進(jìn)行缺省的消息處理。
---- 所以如果正常的消息處理的話,MFC窗口類(lèi)是完全脫離了原來(lái)的窗口過(guò)程,用自己的一套體系結(jié)構(gòu)實(shí)現(xiàn)消息的映射和處理。即先調(diào)用MFC窗口類(lèi)掛上去的窗口過(guò)程,再調(diào)用原先的窗口過(guò)程。并且用戶面對(duì)和消息相關(guān)的參數(shù)不再是死板的wParam和lParam,而是和消息類(lèi)型具體相關(guān)的參數(shù)。比如和消息WM_LbuttonDown相對(duì)應(yīng)的方法OnLButtonDown的兩個(gè)參數(shù)是nFlags和point。nFlags表示在按下鼠標(biāo)左鍵的時(shí)候是否有其他虛鍵按下,point更簡(jiǎn)單,就是表示鼠標(biāo)的位置。
---- 同時(shí)MFC窗口類(lèi)消息傳遞中還提供了兩個(gè)函數(shù),分別為WalkPreTranslateTree和PreTranslateMessage。我們知道利用MFC框架生成的程序,都是從CWinApp開(kāi)始執(zhí)行的,而CWinapp實(shí)際繼承了CWinThread類(lèi)。在CWinThread的運(yùn)行過(guò)程中會(huì)調(diào)用窗口類(lèi)中的WalkPreTranslateTree方法。而WalkPreTranslateTree方法實(shí)際上就是從當(dāng)前窗口開(kāi)始查找愿意進(jìn)行消息翻譯的類(lèi),直到找到窗口沒(méi)有父類(lèi)為止。在WalkPreTranslateTree方法中調(diào)用了PreTranslateMessage方法。實(shí)際上PreTranslateMessage最大的好處是我們?cè)谙⑻幚砬翱梢栽谶@個(gè)方法里面先做一些事情。舉一個(gè)簡(jiǎn)單的例子,比如我們希望在一個(gè)CEdit對(duì)象里,把所有的輸入的字母都以大寫(xiě)的形式出現(xiàn)。我們只需要在PreTranslateMessage方法中判斷message是否為WM_CHAR,如果是的話,把wParam(表示鍵值)由小寫(xiě)字母的值該為大寫(xiě)字母的值就實(shí)現(xiàn)了這個(gè)功能。

---- 繼續(xù)上面的例子,根據(jù)我們對(duì)MFC消息機(jī)制的分析,我們很容易得到除了上面的方法,我們至少還可以在另外兩個(gè)地方進(jìn)行操作。

---- 一:在消息的處理方法里面即OnChar中,當(dāng)然最后我們不再調(diào)用CEdit::OnChar(nChar, nRepCnt, nFlags),而是直接調(diào)用DefWindowProc(WM_CHAR,nChar,MAKELPARAM (nRepCnt,nFlags))。因?yàn)閺奈覀兩厦娴姆治隹梢灾繡Edit::OnChar(nChar, nRepCnt, nFlags)實(shí)際上也就是對(duì)DefWindowProc方法的調(diào)用。

---- 二:我們可以直接重載DefWindowProc方法,對(duì)message類(lèi)型等于WM_CHAR的,直接修改nChar的值即可。

四.小結(jié)
---- 通過(guò)對(duì)MFC類(lèi)庫(kù)的分析和了解,不僅能夠使我們更好的使用MFC類(lèi)庫(kù),同時(shí),對(duì)于我們自己設(shè)計(jì)和實(shí)現(xiàn)框架和類(lèi),無(wú)疑也有相當(dāng)大的幫助。