////////////////////////////////////////////////////////////////////////////////////
/********* 文章系列:MFC技术内幕系列***********/
/************MFC技术内幕系列之(四)***********/
/*****文章题目:MFC消息映射与消息传递内幕******/
/* Copyright(c)2002 bigwhite */
/* All rights Reserved */
/ *********关键字:消息映射,消息传递************/
/* 时间:2002.7.23 */
/* 注释:本文所涉及的程序源代码均在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<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_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;
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);
在挖掘上面源代码的同时你也看到了消息的传递路线。在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文档序列化内幕》
本文地址:http://com.8s8s.com/it/it2320.htm