MFC訊息響應機制分析 (轉)

gugu99發表於2008-03-04
MFC訊息響應機制分析 (轉)[@more@]

一、引言

  VC++的MFC類庫實際上是下C++的一套最為流行的類庫。MFC的結構大大方便了員的程式設計工作,但是為了更加有效、靈活的使用MFC程式設計,瞭解MFC的體系結構往往可以使程式設計工作事半功倍。它合理的封裝了 ,並設計了一套方便的訊息對映機制。但這套機制本身比較龐大和複雜,對它的分析和了解無疑有助於我們寫出更為合理的高效的程式。這裡我們簡單的分析MFC的訊息響應機制,以瞭解MFC是如何對Windows的訊息加以封裝,方便的開發。

二、SDK下的訊息機制實現

  這裡簡單的回顧一下SDK下我們是如何進行Windows的程式開發的。一般來說,Windows的訊息都是和執行緒相對應的。即Windows會把訊息傳送給和該訊息相對應的執行緒。在SDK的下,程式是透過GetMessage函式從和某個執行緒相對應的訊息佇列裡面把訊息取出來並放到一個特殊的結構裡面,一個訊息的結構是一個如下的STRUCTURE。
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
D time;
POINT pt;
}MSG;
  其中hwnd表示和視窗過程相關的視窗的控制程式碼,message表示訊息的ID號,wParam和lParam表示和訊息相關的引數,time表示訊息傳送的時間,pt表示訊息傳送時的滑鼠的位置。
  然後TranslateMessage函式用來把虛鍵訊息翻譯成字元訊息並放到響應的訊息佇列裡面,最後DispatchMessage函式把訊息分發到相關的視窗過程。然後視窗過程根據訊息的型別對不同的訊息進行相關的處理。在SDK程式設計過程中,使用者需要在視窗過程中分析訊息的型別和跟訊息一起的引數的含義,做不同的處理,相對比較麻煩,而MFC把訊息的過程給封裝起來,使使用者能夠透過ClassWizard方便的使用和處理Windows的各種訊息。

三、MFC的訊息實現機制

  可以看到,在MFC的框架結構下,可以進行訊息處理的類的頭裡面都會含有DECLARE_MESSAGE_MAP()宏,這裡主要進行訊息對映和訊息處理函式的宣告。可以進行訊息處理的類的實現檔案裡一般都含有如下的結構。
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)
//{{AFX_MSG_MAP(CInheritClass)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
  這裡主要進行訊息對映的實現和訊息處理函式的實現。
  所有能夠進行訊息處理的類都是基於CCmdTarget類的,也就是說CCmdTarget類是所有可以進行訊息處理類的父類。CCmdTarget類是MFC處理命令訊息的基礎和核心。 同時MFC定義了下面的兩個主要結構:
AFX_MSGMAP_ENTRY
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // ssage
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結構包含了
一個訊息的所有相關資訊,其中
nMessage為Windows訊息的ID號
nCode為控制訊息的通知碼
nID為Windows控制訊息的ID
nLastID表示如果是一個指定範圍的訊息被對映的話,
nLastID用來表示它的範圍。
nSig表示訊息的動作標識
AFX_PMSG pfn 它實際上是一個指向
和該訊息相應的函式的指標。
  而AFX_MSGMAP主要作用是兩個:(一)用來得到基類的訊息對映入口地址。(二)得到本身的訊息對映入口地址。
  實際上,MFC把所有的訊息一條條填入到AFX_MSGMAP_ENTRY結構中去,形成一個陣列,該陣列存放了所有的訊息和與它們相關的引數。同時透過AFX_MSGMAP能得到該陣列的首地址,同時得到基類的訊息對映入口地址,這是為了當本身對該訊息不響應的時候,就呼叫其基類的訊息響應。
  現在來分析MFC是如何讓視窗過程來處理訊息的,實際上所有MFC的視窗類都透過鉤子函式_AfxCbtFilterHook截獲訊息,並且在鉤子函式_AfxCbtFilterHook中把視窗過程設定為AfxWndProc。原來的視窗過程儲存在成員變數m_pfnSuper中。所以在MFC框架下,一般一個訊息的處理過程是這樣的。
  函式AfxWndProc接收Windows操作傳送的訊息。
  函式AfxWndProc呼叫函式AfxCallWndProc進行訊息處理,這裡一個進步是把對控制程式碼的操作轉換成對CWnd的操作。
  函式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)
{
// TODO: Add your message
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訊息機制的分析,我們很容易得到除了上面的方法,我們至少還可以在另外兩個地方進行操作。
  (一)在訊息的處理方法裡面即OnChar中,當然最後我們不再呼叫CEdit::OnChar(nChar, nRepCnt, nFlags),而是直接呼叫DefWindowProc(WM_CHAR,nChar,MAKELPARAM (nRepCnt,nFlags))。因為從我們上面的分析可以知道CEdit::OnChar(nChar, nRepCnt, nFlags)實際上也就是對DefWindowProc方法的呼叫。
  (二)我們可以直接過載DefWindowProc方法,對message型別等於WM_CHAR的,直接修改nChar的值即可。

四、小結

  透過對MFC類庫的分析和了解,不僅能夠使我們更好的使用MFC類庫,同時,對於我們自己設計和實現框架和類,無疑也有相當大的幫助


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1000366/,如需轉載,請註明出處,否則將追究法律責任。

相關文章