MFC的消息響應機制詳解:
1.MFC是Windows下程序設計的最流行的一個類庫,但是該類庫比較龐雜,尤其是它的消息映射機制,更是涉及到很多低層的東西,接下來詳細講解。
2.在講解MFC的消息響應之前先講解一下SDK的消息響應: SDK下的消息機制實現
講解一下SDK下我們是如何進行Windows的程序開發的。一般來說,Windows的消息都是和線程相對應的。即Windows會把消息發送給和該消息相對應的線程。
在SDK的模式下,程序是通過GetMessage函數從和某個線程相對應的消息隊列里面把消息取出來並放到一個特殊的結構里面,一個消息的結構是一個如下的STRUCTURE。
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;
說明一下結構體里的內容:
1)hwnd表示和窗口過程相關的窗口的句柄。
2)message表示消息的ID號。
3)wParam和lParam表示和消息相關的參數。
4)time表示消息發送的時間。
5)pt表示消息發送時的鼠標的位置。
再用TranslateMessage函數用來把虛鍵消息翻譯成字符消息並放到響應的消息隊列里面,最后DispatchMessage函數把消息分發到相關的窗口過程。
然后窗口過程根據消息的類型對不同的消息進行相關的處理。在SDK編程過程中,用戶需要在窗口過程中分析消息的類型和跟消息一起的參數的含義,做不同的處理,
相對比較麻煩,而MFC把消息調用的過程給封裝起來,使用戶能夠通過ClassWizard方便的使用和處理Windows的各種消息。
3.MFC的消息實現機制
在MFC的框架結構下,我們可以清晰的看到,可以進行消息處理的類的頭文件里面都會含有DECLARE_MESSAGE_MAP()宏,這里主要進行消息映射和消息處理函數的聲明。
可以進行消息處理的類的實現文件里一般都含有如下的結構。
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)
END_MESSAGE_MAP()
1)所有能夠進行消息處理的類都是基於CCmdTarget類的,也就是說CCmdTarget類是所有可以進行消息處理類的父類。CCmdTarget類是MFC處理命令消息的基礎和核心。
2)同時MFC定義了下面的兩個主要結構:
AFX_MSGMAP_ENTRY
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID;
UINT nLastID;
UINT nSig;
AFX_PMSG pfn;
};
其中AFX_MSGMAP_ENTRY結構包含了一個消息的所有相關信息:
1)nMessage為Windows消息的ID號。
2)nCode為控制消息的通知碼。
3)nID為Windows控制消息的IDnLastID表。
4)如果是一個指定范圍的消息被映射的話,nLastID用來表示它的范圍。nSig表示消息的動作標識。
5)AFX_PMSG pfn 它實際上是一個指向和該消息相應的執行函數的指針。
AFX_MSGMAP
struct AFX_MSGMAP
{
#ifdef _AFXDLL
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
const AFX_MSGMAP* pBaseMap;
#endif
const AFX_MSGMAP_ENTRY* lpEntries;
};
FX_MSGMAP主要作用是兩個:
1)用來得到基類的消息映射入口地址。
2)得到本身的消息映射入口地址。
實際上,MFC把所有的消息一條條填入到AFX_MSGMAP_ENTRY結構中去,形成一個數組,該數組存放了所有的消息和與它們相關的參數。
同時通過AFX_MSGMAP能得到該數組的首地址,同時得到基類的消息映射入口地址,這是為了當本身對該消息不響應的時候,就調用其基類的消息響應。
分析一下MFC是如何讓窗口過程來處理消息的,實際上所有MFC的窗口類都通過鈎子函數_AfxCbtFilterHook截獲消息,並且在鈎子函數_AfxCbtFilterHook中把窗口過程設定為
AfxWndProc。原來的窗口過程保存在成員變量m_pfnSuper中。
在MFC框架下,一般一個消息的處理過程是這樣的,以下作為說明:
函數AfxWndProc接收Windows操作系統發送的消息。
函數AfxCallWndProc調用CWnd類的方法WindowProc進行消息處理。注意AfxWndProc和AfxCallWndProc都是AFX的API函數。而WindowProc已經是CWnd的一個方法。
所以可以注意到在WindowProc中已經沒有關於句柄或者是CWnd的參數了。
方法WindowProc調用方法OnWndMsg進行正式的消息處理,即把消息派送到相關的方法中去處理。消息是如何派送的呢?實際上在CWnd類中都保存了一個AFX_MSGMAP的
結構,而在AFX_MSGMAP結構中保存有所有我們用ClassWizard生成的消息的數組的入口,我們把傳給OnWndMsg的message和數組中的所有的message進行比較,
找到匹配的那一個消息。實際上系統是通過函數AfxFindMessageEntry來實現的。找到了那個message,實際上我們就得到一個AFX_MSGMAP_ENTRY結構,
而我們在上面已經提到AFX_MSGMAP_ENTRY保存了和該消息相關的所有信息,其中主要的是消息的動作標識和跟消息相關的執行函數。
然后我們就可以根據消息的動作標識調用相關的執行函數,而這個執行函數實際上就是通過ClassWizard在類實現中定義的一個方法。
這樣就把消息的處理轉化到類中的一個方法的實現上。
舉一個簡單的例子,比如在View中對WM_LButtonDown消息的處理就轉化成對如下一個方法的操作。
void CInheritView::OnLButtonDown(UINT nFlags, CPoint point)
{
handler code here and/or call default
CView::OnLButtonDown(nFlags, point);
}
注意這里CView::OnLButtonDown(nFlags, point)實際上就是調用CWnd的Default()方法。 而Default()方法所做的工作就是調用DefWindowProc對消息進行處理。
這實際上是調用原來的窗口過程進行缺省的消息處理。
如果OnWndMsg方法沒有對消息進行處理的話,就調用DefWindowProc對消息進行處理。這是實際上是調用原來的窗口過程進行缺省的消息處理。所以如果正常的消息處理的
話,MFC窗口類是完全脫離了原來的窗口過程,用自己的一套體系結構實現消息的映射和處理。即先調用MFC窗口類掛上去的窗口過程,再調用原先的窗口過程。並且用戶面
對和消息相關的參數不再是死板的wParam和lParam,而是和消息類型具體相關的參數。
比如和消息WM_LbuttonDown相對應的方法OnLButtonDown的兩個參數是nFlags和point。nFlags表示在按下鼠標左鍵的時候是否有其他虛鍵按下,point更簡單,
就是表示鼠標的位置。
同時MFC窗口類消息傳遞中還提供了兩個函數,分別為WalkPreTranslateTree和PreTranslateMessage。我們知道利用MFC框架生成的程序,都是從CWinApp開始執行的,
而CWinapp實際繼承了CWinThread類。在CWinThread的運行過程中會調用窗口類中的WalkPreTranslateTree方法。
而WalkPreTranslateTree方法實際上就是從當前窗口開始查找願意進行消息翻譯的類,直到找到窗口沒有父類為止。
在WalkPreTranslateTree方法中調用了PreTranslateMessage方法。實際上PreTranslateMessage最大的好處是我們在消息處理前可以在這個方法里面先做一些事情。
舉一個簡單的例子:
比如我們希望在一個CEdit對象里,把所有的輸入的字母都以大寫的形式出現。我們只需要在PreTranslateMessage方法中判斷message是否為WM_CHAR,
如果是的話,把wParam(表示鍵值)由小寫字母的值該為大寫字母的值就實現了這個功能。
繼續上面的例子,根據我們對MFC消息機制的分析,我們很容易得到除了上面的方法,我們至少還可以在另外兩個地方進行操作。
a.在消息的處理方法里面即OnChar中,當然最后我們不再調用CEdit::OnChar(nChar, nRepCnt, nFlags),而是直接調用DefWindowProc(WM_CHAR,nChar,MAKELPARAM
(nRepCnt,nFlags))。
因為從我們上面的分析可以知道CEdit::OnChar(nChar, nRepCnt, nFlags)實際上也就是對DefWindowProc方法的調用。
b.我們可以直接重載DefWindowProc方法,對message類型等於WM_CHAR的,直接修改nChar的值即可。
4.今天就寫這些,明天繼續,程序員們要想要精通代碼,最主要的方式之一就是要多練習,多調試,這樣面對bug的時候才能臨危不動。
改變自己,從現在做起-----------久館