天天看點

MFC技術内幕系列之(四)---MFC消息映射與消息傳遞内幕

 ////////////////////////////////////////////////////////////////////////////////////

                     /********* 文章系列:MFC技術内幕系列***********/

                     /************MFC技術内幕系列之(四)***********/

                     /*****文章題目:MFC消息映射與消息傳遞内幕******/

                     /*                            All rights Reserved                        */

                     /   *********關鍵字:消息映射,消息傳遞************/

                     /*      注釋:本文所涉及的程式源代碼均在Microsoft   */

                     /           Visual Studio.net EntERPrise Architect Edition       /

                     /*                   開發工具包提供的源代碼中                  */

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

引言:

    Windows作業系統是以消息為基礎,事件驅動的。作為程式員了解作業系統的消息傳遞機制是非常必要的。Microsoft的MFC又它自己的一套支援Windows作業系統消息機制的技術--消息映射(Message Mapping)和指令傳遞(Command Routing),在這篇文章中我就詳細的挖掘一下MFC的消息映射技術以及指令傳遞技術。

    正文:

                       ///////////////////////////////////////////////

                       /*     1.Windows消息概覽      */

                       //////////////////////////////////////////////

    對于消息,程式員應該不陌生。WM_CREATE,WM_PAINT等等都是Windows程式設計中必不可缺少的組成部分。大多有關MFC Win32程式設計的書籍都将Windows消息分為三大類即:

    * 标準消息:   任何以WM_開頭的消息(WM_COMMAND除外);如:WM_QUIT,WM_CREATE;

    * 指令消息:   WM_COMMAND;

    * 子視窗通知: 由子視窗(大多為控件)産生并發送到該控件所屬的父視窗的消息。(注意:此類消息也                    以WM_COMMAND形式出現)

    消息類型我們已經了解了,下面我們就來看看消息映射是如何工作的:

                       //////////////////////////////////////////////////////

                       /*  2.MFC消息映射網的組成元素 */

                       //////////////////////////////////////////////////////   

   我的前幾篇文章中涉及到了MFC内部建立的一些“網”技術,比如“執行期類型識别網”等,這回我們将建立一個消息映射網,這個網的建立與前面相同的是它也利用了一些神秘的宏。下面我們就來掀開它們的神秘面紗。

   我們先簡單地看看這些宏在程式源檔案中的什麼地方?

   //in xx.h

   class theClass

  {

      ...//

     DECLARE_MESSAGE_MAP

   };

   //in xx.cpp

   BEGIN_MESSAGE_MAP(theClass, baseClass)

 ON_COMMAND( ID_MYCMD, OnMyCommand )

        ON_WM_CREATE

   END_MESSAGE_MAP

   ...//

   這些宏的定義如下:

   //in Afxwin.h

   #define DECLARE_MESSAGE_MAP /

   private: /

 static const AFX_MSGMAP_ENTRY _messageEntries; /

   protected: /

 static const AFX_MSGMAP messageMap; /

 static const AFX_MSGMAP* PASCAL GetThisMessageMap; /

 virtual const AFX_MSGMAP* GetMessageMap const; /

   #define BEGIN_MESSAGE_MAP(theClass, baseClass) /

 const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap /

  { return &theClass::messageMap; } /

 const AFX_MSGMAP* theClass::GetMessageMap const /

 AFX_COMDAT const AFX_MSGMAP theClass::messageMap = /

 { &baseClass::GetThisMessageMap, &theClass::_messageEntries[0] }; /

 AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries = /

 { /

   #define END_MESSAGE_MAP /

  {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } /

 }; /

   DECLARE_MESSAGE_MAP宏為每個類添加了四個東東,包括那個重要的消息映射表messageMap和消息入口結構數組AFX_MSGMAP_ENTRY _messageEntries;BEGIN_MESSAGE_MAP(theClass, baseClass)和END_MESSAGE_MAP宏則初始化了它們,随後我将帶領大家看看這個初始化過程。

                       /*      3.MFC消息映射表       */

                       //////////////////////////////// //////////////

   下面我們看看消息映射表messageMap和消息入口結構AFX_MSGMAP_ENTRY的定義:

 //in Afxwin.h

 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_PTR nSig;   // signature type (action) or pointer to message #

 AFX_PMSG pfn;    // routine to call (or special value)

 };

 struct AFX_MSGMAP

 #ifdef _AFXDLL

 const AFX_MSGMAP* (PASCAL* pfnGetBaseMap);//基類的映射表指針,本程式将使用

 #else

 const AFX_MSGMAP* PBaseMap;

 #endif

 const AFX_MSGMAP_ENTRY* lpEntries;

  其中AFX_MSGMAP結構中包含一個基類的映射表指針和一個指向消息入口結構AFX_MSGMAP_ENTRY的指針。

                       /////////////////////////////////////////////////

                       /*    4.MFC消息映射宏展開     */

  上面的宏展開後代碼如下:(以CMaimFrame為例)

   //in MaimFrm.h

   class CMaimFrame : public CFrameWnd

    ...//

    private:

        static const AFX_MSGMAP_ENTRY _messageEntries;

    protected:

 static const AFX_MSGMAP messageMap;

 static const AFX_MSGMAP* PASCAL GetThisMessageMap;

 virtual const AFX_MSGMAP* GetMessageMap const;

  };

  //in MaimFrm.cpp

    const AFX_MSGMAP* PASCAL CMaimFrame::GetThisMessageMap

  { return &CMaimFrame::messageMap; }

    const AFX_MSGMAP* CMaimFrame::GetMessageMap const

    AFX_COMDAT const AFX_MSGMAP theClass::messageMap =

 { &CFrameWnd::GetThisMessageMap, &CMaimFrame::_messageEntries[0] };

    AFX_COMDAT const AFX_MSGMAP_ENTRY CMaimFrame::_messageEntries =

        {

          {         ...//                      }

                    ...

          {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }

   相信大家看了後大多源代碼都能夠了解,但是AFX_MSGMAP_ENTRY結構還是能夠引起我們的興趣的。下面讓我們看看_messageEntries是如何被初始化的:

   我們還是舉例來說明吧!(還是CMainFrame為例吧)

   BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

 ON_WM_CREATE

        ON_COMMAND( ID_MYCMD, OnMyCommand )

   大家看到了夾在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之間的宏,這些宏可分為基類,一類是Windows預定義消息宏(比如:ON_WM_CREATE,ON_WM_DESTROY等定義在afxmsg_.h中的Message map tables for Windows messages),一類是自定義的ON_COMMAND宏以及類似的如ON_UPDATE_COMMAND_UI等宏 。

   //in afxmsg_.h

   // Message map tables for Windows messages

   #define ON_WM_CREATE /

 { WM_CREATE, 0, 0, 0, AfxSig_is, /

  (AFX_PMSG) (AFX_PMSGW) /

  (static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > (OnCreate)) },

   #define ON_COMMAND(id, memberFxn) /

 { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, /

  static_cast (memberFxn) },

   AFX_MSGMAP_ENTRY結構初始化過程:

   AFX_COMDAT const AFX_MSGMAP_ENTRY CMaimFrame::_messageEntries =

          { WM_CREATE, 0, 0, 0, AfxSig_is,

  (AFX_PMSG) (AFX_PMSGW)

              (static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > (OnCreate)) },

          { WM_COMMAND, CN_COMMAND, (WORD)ID_MYCMD, (WORD)ID_MYCMD, AfxSigCmd_v, /

  static_cast ( OnMyCommand) },       

   現在一切都清楚了吧!

                       //////////////////////////////////////////////////

                       /*    5.MFC消息映射網的連接配接   */

   MFC消息映射網的連接配接也是在初始化過程中完成的,其建立過程很簡單。主要有關成員有:

   private:

   protected:

   和BEGIN_MESSAGE_MAP宏開後的

   AFX_COMDAT const AFX_MSGMAP theClass::messageMap =

 { &baseClass::GetThisMessageMap, &theClass::_messageEntries[0] };

   該宏将pfnGetBaseMap指派為其基類的messageMap位址;将AFX_MSGMAP_ENTRY* lpEntries指派為該類的

_messageEntries[0];

   這樣一個類不僅擁有本類的messageMap,而且還擁有其基類的messageMap,依此類推MFC消息映射網的連接配接

就建立了,最終的基類都是CCmdTarget

                       /*    6.MFC指令傳遞機制概述   */

   有了MFC消息映射網就為指令傳遞打下了堅實的基礎。Win32API程式員都熟悉傳統的API程式設計都有一個

WndProc回調函數來集中處理各種的Windows消息,然而在MFC中我們卻怎麼也看不見WndProc回調函數的蹤影了。而MFC指令傳遞機制恰是為了将各種消息“拐彎抹角”地送到各個對應的"WndProc"函數的一種技術。MFC是如何将各種Windows消息準确的送到期該區的地方呢? MFC使用了鈎子函數等技術來保證其準确性和全面性。

   不知大家是否還記得MFC在注冊視窗類時作了什麼?

   BOOL AFXAPI AfxEndDeferReGISterClass(LONG fToRegister)//部分源代碼

        ...//

        // common initialization

 WNDCLASS wndcls;

 memset(&wndcls, 0, sizeof(WNDCLASS));   // start with NULL defaults

 wndcls.lpfnWndProc = DefWindowProc;

 ...//

   }

  可以看到MFC注冊時将wndcls.lpfnWndProc指派為DefWindowProc函數,那麼實際上是否消息都是由它處理的呢?顯然不可能,MFC又不知道我們要處理什麼消息。那麼還有什麼函數能幫我們處理消息呢?這就是我要講的;  在MFC技術内幕系列之(二)----《 MFC文檔視圖結構内幕》中曾提到“CWnd::CreateEx函數調用了AfxHookWindowCreate(this);後者是幹什麼的呢?其實它與消息映射和指令傳遞有關。”

   實際上MFC指令傳遞機制就是從這裡AfxHookWindowCreate(this)開始的。還是老辦法看看代碼吧:

 //in wincore.cpp

 void AFXAPI AfxHookWindowCreate(CWnd* pWnd)

 if (pThreadState->m_hHookOldCbtFilter == NULL)

  pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,

   _AfxCbtFilterHook, NULL, ::GetCurrentThreadId);

  if (pThreadState->m_hHookOldCbtFilter == NULL)

   AfxThrowMemoryException;

 }

  該函數設定了消息鈎子,其鈎子處理函數為_AfxCbtFilterHook;這裡簡介一下鈎子函數:

  用我的了解,鈎子就是能給你一個在某個消息到達其預設的處理函數之前處理該消息機會的工具。

與鈎子有關的函數主要有三個:

HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId );

LRESULT CallNextHookEx(HHOOK hhk,  int nCode,    WPARAM wParam,   LPARAM lParam  );

BOOL UnhookWindowsHookEx( HHOOK hhk   // handle to hook procedure);

關于這三個函數我也不想多解釋,大家看看MFC有關文檔吧!這裡主要講的是在AfxHookWindowCreate(CWnd* pWnd)中注冊的鈎子函數的類型(hook type)--WH_CBT;

有關WH_CBT,MFC文檔時如是說的:

  Installs a hook procedure that receives notifications useful to a computer-based training (CBT) application. The system calls this function(這裡指的是_AfxCbtFilterHook) before activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event from the system message queue; before setting the keyboard focus; or before synchronizing with the system message queue. A computer-based training (CBT) application uses this hook procedure to receive useful notifications from the system.

                       /////////////////////////////////////////////

                       /*    7.偷換“視窗函數”      */

   這會知道了吧,當發生視窗(包括子視窗)發生被激活,建立,撤銷,最小化等時候,應用程式将調用

_AfxCbtFilterHook函數;下面就讓我們看看_AfxCbtFilterHook函數做了個啥:

// Window creation hooks

 LRESULT CALLBACK _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)

 _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData;

 if (code != HCBT_CREATEWND)

  // wait for HCBT_CREATEWND just pass others on...

  return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,

   wParam, lParam);

 ...//   CWnd* pWndInit = pThreadState->m_pWndInit;

  HWND hWnd = (HWND)wParam;

  WNDPROC oldWndProc;

  if (pWndInit != NULL)

  {

                 #ifdef _AFXDLL

   AFX_MANAGE_STATE(pWndInit->m_pModuleState);

                 #endif

   // the window should not be in the permanent map at this time

   ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);

   // connect the HWND to pWndInit...

   pWndInit->Attach(hWnd);

   // allow other subclassing to occur first

   pWndInit->PreSubclassWindow;//***

   WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr;

   ASSERT(pOldWndProc != NULL);

   WNDPROC afxWndProc = AfxGetAfxWndProc;//***

   oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,

    (DWORD_PTR)afxWndProc);//***

   ASSERT(oldWndProc != NULL);

   if (oldWndProc != afxWndProc)

    *pOldWndProc = oldWndProc;

   pThreadState->m_pWndInit = NULL;

  }

 lCallNextHook:

 LRESULT lResult = CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,

  wParam, lParam);

 #ifndef _AFXDLL

 if (bContextIsDLL)

  ::UnhookWindowsHookEx(pThreadState->m_hHookOldCbtFilter);

  pThreadState->m_hHookOldCbtFilter = NULL;

 return lResult;

 void CWnd::PreSubclassWindow

{

 // no default processing

 // always indirectly Accessed via AfxGetAfxWndProc

 WNDPROC AFXAPI AfxGetAfxWndProc

 return AfxGetModuleState->m_pfnAfxWndProc;

 return &AfxWndProc;

  原來_AfxCbtFilterHook函數偷換了視窗函數,将原來的DefWndProc換成AfxWndProc函數.

                       ////////////////////////////////////////////////////

                       /*   8.MFC的“WndProc”函數   */

   AfxWndProc函數就可以說是MFC的“WndProc”函數,它也是MFC中消息傳遞的開始,其代碼如下:

//in wincore.cpp

// The WndProc for all CWnd's and derived classes

LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)

 // special message which identifies the window as using AfxWndProc

 if (nMsg == WM_QUERYAFXWNDPROC)

  return 1;

 // all other messages route through message map

 CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

 ASSERT(pWnd != NULL);

 ASSERT(pWnd->m_hWnd == hWnd);

 return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);

}

// Official way to send message to a CWnd

LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,

 WPARAM wParam = 0, LPARAM lParam = 0)

 MSG oldState = pThreadState->m_lastSentMsg;   // save for nesting

 pThreadState->m_lastSentMsg.hwnd = hWnd;

 pThreadState->m_lastSentMsg.message = nMsg;

 pThreadState->m_lastSentMsg.wParam = wParam;

 pThreadState->m_lastSentMsg.lParam = lParam;

         ...//

       // in debug builds and warn the user.

 LRESULT lResult;

 TRY

#ifndef _AFX_NO_OCC_SUPPORT

  // special case for WM_DESTROY

  if ((nMsg == WM_DESTROY) && (pWnd->m_pCtrlCont != NULL))

   pWnd->m_pCtrlCont->OnUIActivate(NULL);

#endif

               // special case for WM_INITDIALOG

  CRect rectOld;

  DWORD dwStyle = 0;

  if (nMsg == WM_INITDIALOG)

   _AfxPreInitDialog(pWnd, &rectOld, &dwStyle);

  // delegate to object's WindowProc

  lResult = pWnd->WindowProc(nMsg, wParam, lParam);//***

  // more special case for WM_INITDIALOG

   _AfxPostInitDialog(pWnd, rectOld, dwStyle);

 CATCH_ALL(e)

  ...//

 END_CATCH_ALL

 pThreadState->m_lastSentMsg = oldState;

// main WindowProc implementation

LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

 // OnWndMsg does most of the work, except for DefWindowProc call

 LRESULT lResult = 0;

 if (!OnWndMsg(message, wParam, lParam, &lResult))

  lResult = DefWindowProc(message, wParam, lParam);

    BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)

 mmf.pfn = 0;

        // special case for commands

 if (message == WM_COMMAND)

  if (OnCommand(wParam, lParam))

   lResult = 1;

   goto LReturnTrue;

  return FALSE;

        // special case for notifies

 if (message == WM_NOTIFY)

  NMHDR* pNMHDR = (NMHDR*)lParam;

  if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))

        const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap;

 UINT iHash; iHash = (LOWORD((DWORD_PTR)pMessageMap) ^ message) & (iHashMax-1);

 AfxLockGlobals(CRIT_WINMSGCACHE);

 AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash];

 const AFX_MSGMAP_ENTRY* lpEntry;

 if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)

  // cache hit

  lpEntry = pMsgCache->lpEntry;

  AfxUnlockGlobals(CRIT_WINMSGCACHE);

  if (lpEntry == NULL)

   return FALSE;

  // cache hit, and it needs to be handled

  if (message < 0xC000)

   goto LDispatch;

  else

   goto LDispatchRegistered;

 else

  // not in cache, look for it

  pMsgCache->nMsg = message;

  pMsgCache->pMessageMap = pMessageMap;

  #ifdef _AFXDLL

  for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;

   pMessageMap = (*pMessageMap->pfnGetBaseMap))

  #else

  for (/* pMessageMap already init'ed */; pMessageMap != NULL;

   pMessageMap = pMessageMap->pBaseMap)

  #endif

   // Note: catch not so common but fatal mistake!!

   //      BEGIN_MESSAGE_MAP(CMyWnd, CMyWnd)

   ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap));

   ASSERT(pMessageMap != pMessageMap->pBaseMap);

   if (message < 0xC000)

   {

    // constant window message

    if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,

     message, 0, 0)) != NULL)

    {

     pMsgCache->lpEntry = lpEntry;

     AfxUnlockGlobals(CRIT_WINMSGCACHE);

     goto LDispatch;

    }

   }

   else

     // registered windows message

           lpEntry = pMessageMap->lpEntries;

   while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL)

     UINT* pnID = (UINT*)(lpEntry->nSig);

     ASSERT(*pnID >= 0xC000 || *pnID == 0);

      // must be successfully registered

     if (*pnID == message)

     {

      pMsgCache->lpEntry = lpEntry;

      AfxUnlockGlobals(CRIT_WINMSGCACHE);

      goto LDispatchRegistered;

     }

     lpEntry++;      // keep looking past this one

  pMsgCache->lpEntry = NULL;

   LDispatch:

 ASSERT(message < 0xC000);

 mmf.pfn = lpEntry->pfn;

 switch (lpEntry->nSig)

 default:

  ASSERT(FALSE);

  break;

 case AfxSig_b_D_v:

  lResult = (this->*mmf.pfn_b_D)(CDC::FromHandle(reinterpret_cast(wParam)));

  LDispatchRegistered:    // for registered windows messages

 ASSERT(message >= 0xC000);

 ASSERT(sizeof(mmf) == sizeof(mmf.pfn));

 lResult = (this->*mmf.pfn_l_w_l)(wParam, lParam);

  LReturnTrue:

 if (pResult != NULL)

  *pResult = lResult;

 return TRUE;

   該源代碼有整整700行,不知道是不是MFC源碼中最長的一個函數,在這裡我隻列出部分有代表性的源碼。從源代碼中大家也可以看得出該函數主要用于分辨消息并将消息交于其處理函數。MFC為了加快消息得分檢速度在AfxFindMessageEntry函數中甚至使用了彙編代碼。

在CWnd::OnWndMsg中得不到處理的消息則交給CWnd::DefWindowProc(相當于MFC的預設DefWindowProc函數)處理,其代碼為:

 // Default CWnd implementation

 LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)

 if (m_pfnSuper != NULL)

  return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);

 WNDPROC pfnWndProc;

 if ((pfnWndProc = *GetSuperWndProcAddr) == NULL)

  return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam);

  return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);

  CWnd::DefWindowProc這調用了傳統win32程式員熟悉的::DefWindowProc(m_hWnd, nMsg, wParam, lParam);

在挖掘上面源代碼的同時你也看到了消息的傳遞路線。在MFC中CWnd以及派生于CWnd的類都擁有虛函數

CWnd::WndProc(...)。

                       /////////////////////////////////////////////////////

                       /* 9.MFC各類消息的"行走路徑 " */

   在篇頭我就将消息分了類而且到目前我們已經了解了消息的傳遞機制了,下面我就具體的某類消息來看看其傳遞路徑。

   無論什麼消息都有AfxWndProc進入,到達CWnd::OnWndMsg函數分檢消息;

   對于标準消息:

       标準消息一般都沿其消息映射表從本類到父類逐層查找其處理函數,若沒查到着交給::DefWindowProc        處理。

   對于指令消息:

       指令消息除了能像标準消息一樣從本類到父類逐層查找其處理函數外,有時他們可能還要拐彎。

       再回頭看看CWnd::OnWndMsg源碼是如何處理WM_COMMAND消息的:

        if (message == WM_COMMAND)

        原來交給了CWnd::OnCommand(wParam, lParam)

        下面看看一個SDI程式中指令消息在Frame,View,Document以及CWinApp對象之間的傳遞路線。

        //in winfrm.cpp

        BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)

 // return TRUE if command invocation was attempted

       {

 HWND hWndCtrl = (HWND)lParam;

 UINT nID = LOWORD(wParam);

 CFrameWnd* pFrameWnd = GetTopLevelFrame;

 ASSERT_VALID(pFrameWnd);

 if (pFrameWnd->m_bHelpMode && hWndCtrl == NULL &&

  nID != ID_HELP && nID != ID_DEFAULT_HELP && nID != ID_CONTEXT_HELP)

  // route as help

  if (!SendMessage(WM_COMMANDHELP, 0, HID_BASE_COMMAND+nID))

   SendMessage(WM_COMMAND, ID_DEFAULT_HELP);

  return TRUE;

 // route as normal command

 return CWnd::OnCommand(wParam, lParam);

        }

        //in wincore.cpp

        // CWnd command handling

        BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)

       { ...//

         return OnCmdMsg(nID, nCode, NULL, NULL);//CFrameWnd::OnCmdMsg

       }

      // CFrameWnd command/message routing

       BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,

 AFX_CMDHANDLERINFO* pHandlerInfo)

      {

 CPUshRoutingFrame push(this);

 // pump through current view FIRST

 CView* pView = GetActiveView;

 if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

 // then pump through frame

 if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

 // last but not least, pump through app

 CWinApp* pApp = AfxGetApp;

 if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

 return FALSE;

       Frame的COMMAND傳遞順序是View--->Frame本身-->CWinApp對象。

       //in viewcore.cpp

       BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,

 // first pump through pane

 // then pump through document

 if (m_pDocument != NULL)

  // special state for saving view before routing to document

  CPushRoutingView push(this);

  return m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

      }

      View的COMMAND傳遞順序是View本身--->Document

     //in doccore.cpp

     BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra,

     {

 if (CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

 // otherwise check template

 if (m_pDocTemplate != NULL &&

   m_pDocTemplate->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

     }

     Document的COMMAND傳遞順序是Document本身--->Document Template

     由這個例子我們可以清楚地看到WM_COMMAND的傳遞路徑了。

  對于子視窗通知:由于子視窗通知通常以WM_COMMAND形式出現,是以它的傳遞路徑也大緻與WM_COMMAND相同,這裡就不詳述了。  

                       ///////////////////////////////////////////

                       /*       10.收尾工作          */

                       /////////////////////////////////////////

  至此,MFC消息映射與消息傳遞的内幕已基本被揭開,若想更深刻的了解,你就得在平時的程式開發中奪觀察多思考了。

                       /*       11.下期預告          */

    MFC技術内幕系列之(五)-------《MFC文檔序列化内幕》

繼續閱讀