消息如何流動 ----------- 注意 GetMessageMap為虛函數
一、傳統SDK程序的消息循環在傳統的SDK程序中,消息循環是很簡單的,也許你不信,那我們就看看下面這段代碼吧:
#include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("HelloWin") ; WNDCLAS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.lpszClassName = szAppName ; RegisterClass (&wndclass); hwnd = CreateWindow( szAppName,……,NULL); ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: ……… case WM_PAINT: ……… case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }在WinMain 中 CreateWindow通過一個參數將創建的窗口和窗口類(見"窗口類的誕生"一文)聯系起來,這樣該窗口的所有消息都將發送到該窗口類的窗口函數WndProc,其后WndProc根據不同的消息給予不同的動作。

在傳統的SDK程序中消息循環是非常簡單的,并且將窗口和窗口函數綁定在一起。而在MFC中就出現了問題,比如CDocument類,不是窗口,所以沒有窗口類,但是我也想讓它響應消息,怎辦?問題不僅僅如此,我們再看看MFC的消息,就會發現更多問題。
MFC將消息分為三大類:1.標準消息,即除WM_COMMAND之外的任何WM_開頭的消息,任何派生自CWnd的類都可以接受該消息,并按照繼承關系接受(如從CScrollView到CView再到CWnd)。2.命令消息,即WM_COMMAND,任何派生自CCmdTarget的類,兼可接受該消息,接受順序如下圖所示,其中標號標注了接受消息的順序,箭頭代表調用順序 :

圖1 消息的拐彎流動
3.Control Notification,通知類消息,也以WM_COMMAND形式出現,由控件產生,通知其父窗口。

知道了MFC消息流動的要求,那MFC是怎樣實現的呢?當一個消息出現時,Application FrameWork怎么知道將該消息發送給哪個對象的呢?其實都是CCmdTarget類在作怪,所有能夠接受消息的類都必須繼承于CCmdTarget類,因為這些類都一個共同的特征:含有DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP三個宏。啊!就這三個宏組織了一張龐大的消息映射網,也許你不信,那我們就看看這三個宏是怎樣定義的:
#define DECLARE_MESSAGE_MAP()\ private:\ static const AFX_MSGMAP_ENTRY _messageEntries[];\ protected: static AFX_DATA const AFX_MSGMAP messageMap;\ virtual const AFX_MSGMAP* GetMessageMap() const;\ #define BEGIN_MESSAGE_MAP(theClass, baseClass)\ const AFX_MSGMAP* theClass::GetMessageMap() const\ {return &theClass::messageMap;}\ AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \ {&baseClass::messageMap, &theClass::_messageEntries[0]};\ const AFX_MSGMAP_ENTRY theClass::_messageEntries[]=\ {\ #define END_MESSAGE_MAP()\ {0,0,0,0,AfxSig_end,(AFX_pMSG)0}\ };\ typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void); struct AFX_MSGMAP_ENTRY { UINT nMessage; UINT nCode; UINT nID; UINT nLastID; UINT nSig; AFX_PMSG pfn; }; struct AFX_MSGMAP { const AFX_MSGMAP* pBaseMap; const AFX_MSGMAP_ENTRY* lpEntries; };可以看出DECLARE_MESSAGE_MAP宏在其類中申請了一個全局結構和獲得該結構的函數,而在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之間填寫剛才的全局結構,將消息和對應的處理函數聯系起來,并通過AFX_MSGMAP中的pBaseMap指針,將各類按繼承順序連接起來,從而提供消息流動的道路(即消息的直流,滿足標準消息流動的要求)。
下面我們舉個例子:
CMyWnd : public CWnd { …… DECLARE_MESSAGE_MAP() } BEGIN_MESSAGE_MAP(CMyWnd,CWnd) ON_WM_CREATE() ON_WM_PAINT() END_MESSAGE_MAP()被展開后,代碼如下:
CMyWnd:public CWnd { …… private: static const AFX_MSGMAP_ENTRY _messageEntries[]; protected: static AFX_DATA const AFX_MSGMAP messageMap; virtual const AFX_MSGMAP* GetMessageMap() const; } const AFX_MSGMAP* CMyWnd::GetMessageMap() const { return &CMyWnd::messageMap;} AFX_DATADEF const AFX_MSGMAP CMyWnd::messageMap= {&CWnd::messageMap, &CMyWnd::_messageEntries[0]}; const AFX_MSGMAP_ENTRY CMyWnd::_messageEntries[]= { {WM_CREATE,0,0,0,AfxSig_is, (AFX_PMSG)(AFX_PMSGW)(int(AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT))OnCreate}, {WM_PAINT,0,0,0,AfxSig_vv, (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))OnPaint}, {0,0,0,0,AfxSig_end,(AFX_PMSG)0} };這樣 WM_CREATE,WM_PAINT 在消息網中流動,當流到CMyWnd類的 messageMap 結構時,發現有該消息的記錄,則調用記錄中記載的 OnCreate 和 OnPaint 函數,進行響應消息,從而完成了 Windows 消息驅動機制。

我們已經建立了一張消息流動網絡,但是消息是怎樣從產生到響應函數收到該消息,而且標準消息需要直流,命令消息還有許多拐彎(在標題二中可以看到)。不要緊張,我們只需要看看MFC是怎樣實現的。
不管怎么說,對 Windows 系統來說都是一樣的,它都是不斷地用GetMessage(或者其它)從消息隊列中取出消息,然后用DispatchMessage將消息發送到窗口函數中去。在"窗口類的誕生"中知道,MFC將所有的窗口處理函數都注冊成DefWndProc,那是不是MFC將所有的消息都發送到DefWndProc中去了呢?很抱歉不是,而是都發送到了AfxWndProc函數去了。你可能要問為什么,這也是我想知道的,那我們就看看MFC代碼吧:
BOOL CWnd::CreateEx(……) { …… PreCreateWindow(cs); AfxHookWindowCreate(this); HWND hWnd = ::CreateWindowEx(……); …… } void AFXAPI AfxHookWindowCreate(CWnd *pWnd) { …… pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook,NULL,::GetCurrentThreadId()); …… } _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam) { …… if(!afxData.bWin31) { _AfxStandardSubclass((HWND)wParam); } …… } void AFXAPI _AfxStandardSubclass(HWND hWnd) { …… oldWndProc = (WNDPROC)SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)AfxGetAfxWndProc()); } WNDPROC AFXAPI AfxGetAfxWndProc() { …… return &AfxWndProc; }看了上面的代碼,不知你有沒有了然于胸的感覺"啊,原來是這樣呀!"其實MFC在PreCreateWindow注冊窗口類之后,在創建窗口之前,調用了AfxHookWindowCreate函數,該函數設置了鉤子(鉤子用SetWinowsHook或者SetWindowsHookEx設置,這樣消息有滿足設置的消息時,系統就發送給你設置的函數,這里是_AfxCbtFilterHook函數),這樣每次創建窗口的時候,該函數就將窗口函數修改成AfxWndProc。至于為什么這樣做嗎?那是為了包容新的3D控件而又同MFC2.5兼容。

消息的起點是AfxWndProc函數,所有的消息都被發送到AfxWndProc,也從AfxWndProc再次流向各自的消息響應函數的,怎么流的呢?那只有MFC知道:
LRESULT CALLBACK AfxWndProc(…….) { …… return AfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam); } LRESULT AFXAPI AfxCallWndProc(……) { …… lResult = pWnd->WindowProc(nMsg,wParam,lParam); …… } LRESULT CWnd::WindowProc(……) { …… if(!OnWndMsg(message,wParam,lParam,&lResult)) lResult = DefWindowProc(message,wParam,lParam); …… } BOOL CWnd::OnWndMsg(……)//該函數原來太過龐大,被我改造了一下,只反映意思,不能執行 { …… if(message == WM_COMMAND) OnCommand(wParam,lParam); if(message == WM_NOTIFY) OnNotify(wParam,lParam,&lResult); pMessage = GetMessageMap(); for(; pMessageMap!=NULL; pMessageMap = pMessageMap->pBaseMap) { if((lpEntry=AfxFindMessageEntry(pMessageMap->lpEntries, message,0,0))!=NULL) break; } (this->*(lpEntry->pnf))(……);//調用消息響應函數 } AFX_MSGMAP_ENTRY AfxFindMessageEntry(……) { …… while(lpEntry->nSign!=AfxSig_end) { if(lpEntry->nMessage==nMsg&&lpEntry->nCode==nCode&&nID>=lpEntry->nID &&nID<=lpEntry->nLastID) { return lpEntry; } lpEntry++; } …… }消息被發送到對應窗口的OnWndMsg后,然后根據消息的類型采取相應動作:如果是標準消息,則檢查但前類中有無處理函數(由AfxFindMessageEntry實現),若沒有,就在其父親類中找(通過pMessageMap->pBaseMap實現),這樣望上順序搜索消息網,搜索結束也找不到處理函數,那么回到WindowProc函數調用默認DefWindowProc函數;如果是命令消息或通知消息則發送到OnCommand或者OnNotify函數中去處理,來實現消息的拐彎流動:
BOOL CWnd::OnCommand(WPARAM wParam,LPARAM lParam) { …… return OnCmdMsg(nID,nCode,NULL,NULL); } BOOL CFrameWnd::OnCmdMsg(……) { CView* pView = GetActiveView(); if(pView!=NULL&&pView->OnCmdMsg(……)) //相當于圖1中Frame指向View的箭頭 return TRUE; if(CWnd::OnCmdMsg(……)) //圖1中Frame自身 return TRUE; CWinApp *pApp = AfxGetApp(); if(pApp != NULL && pApp->OnCmdMsg(……)) //圖1中CWinApp對象 return TRUE; return FALSE; } BOOL CView::OnCmdMsg(……) { if(CWnd::OnCmdMsg(……)) //圖1中View本身 return TRUE; if(m_pDocument!=NULL) m_pDocument->OnCmdMsg(……);//圖1中View到Doc箭頭 …… } BOOL CDocument::OnCmdMsg(……) { if(CCmdTarget::OnCmdMsg(……)) //圖1中Doc本身 return TRUE; if(m_pDocTemplate!=NULL&&m_pDocTemplate->OnCmdMsg(……))//圖1中Doc Template return TRUE; return FALSE; } BOOL CCmdTarget::OnCmdMsg(……)//注:CWnd沒有重載CCmdTarget的OnCmdMsg { …… for(pMessageMap=GetMessageMap();pMessageMap!=NULL; pMessageMap=pMessageMap->pBaseMap) { lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,……); if(lpEntry!=NULL) return DispatchCmdMsg(……lpEntry->pfn,……); } return FALSE; }從代碼中可以看出,OnCmdMsg各自調用的順序剛好就是圖1中所要求的順序,這樣也就實現了消息的拐彎流動,最后DispatchCmdMsg 函數是調用找到的消息處理函數處理消息。至此消息從出現到找到處理函數已經完成!