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 /
  { return &theClass::messageMap; } /
 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
  { return &CMaimFrame::messageMap; }
    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 )
   END_MESSAGE_MAP
  
   大家看到了夹在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) },       
          {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
 };
   现在一切都清楚了吧!

                       //////////////////////////////////////////////////
                       /*    5.MFC消息映射网的连接   */
                       //////////////////////////////////////////////////
   MFC消息映射网的连接也是在初始化过程中完成的,其建立过程很简单。主要有关成员有:
   private:
        static const AFX_MSGMAP_ENTRY _messageEntries;
   protected:
 static const AFX_MSGMAP messageMap;
   和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函数做了个啥:
 //in wincore.cpp
// 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);

   // subclass the window with standard AfxWndProc
   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;
 }
 #endif
 return lResult;
 }

 void CWnd::PreSubclassWindow
{
 // no default processing
 }
 
 // always indirectly Accessed via AfxGetAfxWndProc
 WNDPROC AFXAPI AfxGetAfxWndProc
 {
 #ifdef _AFXDLL
 return AfxGetModuleState->m_pfnAfxWndProc;
 #else
 return &AfxWndProc;
 #endif
 }

  原来_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)
{
 _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData;
 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
  if (nMsg == WM_INITDIALOG)
   _AfxPostInitDialog(pWnd, rectOld, dwStyle);
 }
 CATCH_ALL(e)
 {
  ...//
 }
 END_CATCH_ALL

 pThreadState->m_lastSentMsg = oldState;
 return lResult;
}

// 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);
 return lResult;
}

    BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
 LRESULT lResult = 0;

 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))
   goto LReturnTrue;
  return FALSE;
 }
        ...//
        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)
  #ifdef _AFXDLL
   ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap));
  #else
   ASSERT(pMessageMap != pMessageMap->pBaseMap);
  #endif

   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;
  AfxUnlockGlobals(CRIT_WINMSGCACHE);
  return FALSE;
 }
   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)));
  break;
        ...//
  LDispatchRegistered:    // for registered windows messages
 ASSERT(message >= 0xC000);
 ASSERT(sizeof(mmf) == sizeof(mmf.pfn));
 mmf.pfn = lpEntry->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);
 else
  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)
 {
  if (OnCommand(wParam, lParam))
  {
   lResult = 1;
   goto LReturnTrue;
  }
  return FALSE;
 }
        原来交给了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 TRUE if command invocation was attempted
       { ...//
         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))
  return TRUE;

 // then pump through frame
 if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
  return TRUE;

 // last but not least, pump through app
 CWinApp* pApp = AfxGetApp;
 if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
  return TRUE;

 return FALSE;
       }
       Frame的COMMAND传递顺序是View--->Frame本身-->CWinApp对象。
       
       //in viewcore.cpp
       BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,
 AFX_CMDHANDLERINFO* pHandlerInfo)
      {
 // first pump through pane
 if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
  return TRUE;

 // 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);
 }

 return FALSE;
      }
      View的COMMAND传递顺序是View本身--->Document

     //in doccore.cpp
     BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra,
 AFX_CMDHANDLERINFO* pHandlerInfo)
     {
 if (CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
  return TRUE;

 // otherwise check template
 if (m_pDocTemplate != NULL &&
   m_pDocTemplate->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
  return TRUE;

 return FALSE;
     }
     Document的COMMAND传递顺序是Document本身--->Document Template
     由这个例子我们可以清楚地看到WM_COMMAND的传递路径了。
 
  对于子窗口通知:由于子窗口通知通常以WM_COMMAND形式出现,所以它的传递路径也大致与WM_COMMAND相同,这里就不详述了。  

                       ///////////////////////////////////////////
                       /*       10.收尾工作          */
                       /////////////////////////////////////////
  至此,MFC消息映射与消息传递的内幕已基本被揭开,若想更深刻的理解,你就得在平时的程序开发中夺观察多思考了。

                       /////////////////////////////////////////
                       /*       11.下期预告          */
                       /////////////////////////////////////////

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

时间: 2024-11-02 18:39:33

MFC技术内幕系列之(四)---MFC消息映射与消息传递内幕的相关文章

MFC深入浅出-消息映射的实现

消息映射的实现   Windows消息概述   Windows 应用程序的输入由Windows系统以消息的形式发送给应用程序的窗口.这些窗口通过窗口过程来接收和处理消息,然后把控制返还给Windows.   消息的分类   队列消息和非队列消息   从消息的发送途径上看,消息分两种:队列消息和非队列消息.队列消息送到系统消息队列,然后到线程消息队列:非队列消息直接送给目的窗口过程. 这里,对消息队列阐述如下: Windows 维护一个系统消息队列(System message queue),每个

mfc-visual c++中,MFC的消息映射宏背后的实现原理搞不明白?有谁能解释一下宏的知识。

问题描述 visual c++中,MFC的消息映射宏背后的实现原理搞不明白?有谁能解释一下宏的知识. BEGIN_MESSAGE_MAP() ...... ON_COMMAND() ........ END_MESSAGE_MAP() 这背后怎么执行,生成的,完全不知所云,只是想了解一下的原理,这样用是会用,但是不明不白 的,心里有些疑惑. 解决方案 MFC消息映射BEGIN_MESSAGE_MAP详解 解决方案二: MFC消息映射BEGIN_MESSAGE_MAP详解,我就是看的这个,http

剖析MFC六大关键技术(五六)--消息映射与命令传递

说到消息,在MFC中,"最熟悉的神秘"可算是消息映射,那是我们刚开始接触MFC时就要面对的东西.有过SDK编程经验的朋友转到MFC编程的时候,一下子觉得什么都变了样.特别是窗口消息及对消息的处理跟以前相比,更是风马牛不相及的.如文档不是窗口,是怎样响应命令消息的呢? 初次用MFC编程,我们只会用MFC ClassWizard为我们做大量的东西,最主要的是添加消息响应.记忆中,如果是自已添加消息响应,我们应何等的小心翼翼,对BEGIN_MESSAGE_MAP()--END_MESSAGE

MFC教程(4)-- 消息映射的实现(2)

但是在当前例子中,当前对象的类CTview没有覆盖该函数,所以CWnd的WindowProc被调用. 这个函数把下一步的工作交给OnWndMsg函数来处理.如果OnWndMsg没有处理,则交给DefWindowProc来处理. OnWndMsg和DefWindowProc都是CWnd类的虚拟函数. OnWndMsg的原型如下: BOOL CWnd::OnWndMsg( UINT message, WPARAM wParam, LPARAM lParam,RESULT*pResult ); 该函数

dll动态库-MFC 动态创建控件不响应消息

问题描述 MFC 动态创建控件不响应消息 按照工程的要求,我必须建立一个类(动态链接库中),这个类是从CWnd派生的,里面的控件全部要求动态生成,而我这个类会作为一个Dialog的成员变量. 目前动态创建控件可以实现,但是控件却不响应消息,我认为是消息只在Dialog中,没法传到其成员变量中,不知道是不是这个原因--请大神帮忙! 我做了一个很简单的程序,请大神帮忙分析一下,灰常灰常感谢~ 解决方案 // MFCApplication1Dlg.h : 头文件 protected: CTestWnd

MFC消息映射的原理:笔记

多态的实现机制有两种,一是通过查找绝对位置表,二是查找名称表:两者各有优缺点,那么为什么mfc的消息映射采用了第二种方法,而不是c++使用的第一种呢?因为在mfc的gui类库是一个庞大的继承体系,而里面的每个类有很多成员函数(只说消息反映相关的成员函数啊),而且在派生类中,需要改写的也比较少(我用来做练习的程序就是那么一两个,呵呵).那么用c++的虚函数的实现机制会导致什么问题呢?就是大量虚表的建立使得空间浪费掉很多.   嗯-怎么办呢?于是各大c++名库(比如QT,MFC,VCL-)在消息映射

mfc-谁能详细解释下MFC消息映射的原理是什么?

问题描述 谁能详细解释下MFC消息映射的原理是什么? 宏不是代码替换么?那么MFC是怎么把消息循环和消息处理函数对接在一起的?我想同时关联几个消息函数到一个消息,怎么做呢? 解决方案 http://www.cnblogs.com/lidabo/p/3694726.html

WPF技术触屏上的应用系列(四): 3D效果图片播放器(图片立体轮放、图片立体轮播、图片倒影立体滚动)效果实现

原文:WPF技术触屏上的应用系列(四): 3D效果图片播放器(图片立体轮放.图片立体轮播.图片倒影立体滚动)效果实现         去年某客户单位要做个大屏触屏应用,要对档案资源进行展示之用.客户端是Window7操作系统,54寸大屏电脑电视一体机.要求有很炫的展示效果,要有一定的视觉冲击力,可触控操作.当然满足客户的要求也可以有其它途径.但鉴于咱是搞 .NET技术的,首先其冲想到的微软WPF方面,之前对WPF的了解与学习也只是停留在比较浅的层面,没有进一步深入学习与应用.所以在项目接来以后,

MFC 消息映射表 及 相关宏定义

MFC相关技术说明:<可参阅MSDN MFC TNO 5> MFC 中通过通过不同于SDK的switch的方法来处理WINDOWS消息, 由消息映射表(Message Map)和虚函数多态来处理指定的窗体消息   1 声明一个消息映射表(Message Map) 在能处理消息的类中中添加宏 DECLARE_MESSAGE_MAP()   注 深入浅出MFC中有更详尽系统的概述 这里只介绍几个宏定义 这个宏实现了3个功能: 1 私有数据成员声明 AFX_MESSAGEMAP_ENTRY _mes