MFC消息映射与消息传递内幕

MFC消息映射与消息传递内幕

Windows操作系统是以消息为基础,事件驱动的。作为程序员了解操作系统的消息传递机制是非常必要的。MicrosoftMFC有它自己的一套支持Windows操作系统消息机制的技术--消息映射(Message Mapping)和命令传递(Command Routing),这篇文章就详细挖掘一下MFC的消息映射技术以及命令传递技术。

1. Windows消息概览

对于消息,程序员应该不陌生。WM_CREATEWM_PAINT等等都是Windows程序设计中必不可缺少的组成部分。大多有关MFC Win32编程的书籍都将Windows消息分为三大类即:

  1. 标准消息:任何以WM_开头的消息(WM_COMMAND除外);如:WM_QUITWM_CREATE
  2. 命令消息:WM_COMMAND
  3. 子窗口通知:由子窗口(大多为控件)产生并发送到该控件所属的父窗口的消息(注意:此类消息也以WM_COMMAND形式出现)。

2. 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_MAPEND_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<AFX_PMSG> (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<AFX_PMSG> ( 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_CBTMFC文档时如是说的:

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;

 union MessageMapFunctions mmf;

 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<HDC>(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);

在挖掘上面源代码的同时你也看到了消息的传递路线。在MFCCWnd以及派生于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;

   }

   FrameCOMMAND传递顺序是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;

  }

  ViewCOMMAND传递顺序是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;

 }

DocumentCOMMAND传递顺序是Document本身--->Document Template

由这个例子我们可以清楚地看到WM_COMMAND的传递路径了。

对于子窗口通知:由于子窗口通知通常以WM_COMMAND形式出现,所以它的传递路径也大致与WM_COMMAND相同,这里就不详述了。

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

编程原则:

  1. 对于所有有返回值的函数,都应检查返回值,除非你能保证该函数绝不会出错,或者不关心它是否会出错
  2. 一些函数的返回错误,需要通过其他函数获得错误信息,如accept返回INVALID_SOCKET表示accept失败,为了查出具体原因,应立刻用WSAGETLASTERROR获得错误信息。
  3. 有些函数通过异常机制抛出错误,应该用TRY_CATCH语句来检查错误
  4. 程序员对于能处理的错误,应该用底层处理,对于不能处理的错误,已改制用户,让他们决定怎么处理。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值