我们在《MFC消息流动的内部实现》中已经看到,当消息为命令消息(WM_COMMAND)时,在CWnd::OnWndMsg中将交由OnCommand虚函数来处理。该处的OnCommand函数不一定是CWnd:: OnCommand,具体情况要视this指针所指对象而定。在MFC中以下数个类都改写了OnCommand虚函数:
class CWnd : public CCmdTarget
class CFrameWnd : public CWnd
class CMDIFrameWnd : public CFrameWnd
class CSplitterWnd : public CWnd
class CPropertySheet : public CWnd
class COlePropertyPage : public CDialog
现在我们假设有一个命令消息从CFrameWnd传进来(该类对象的命令处理能够让我们对整个命令传递路径有全面地把握)。于是,在WINFRM.CPP中有:
BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)
// return TRUE if command invocation was attempted
{
……
// route as normal command
return CWnd::OnCommand(wParam, lParam);
}
在WINCORE.CPP中有:
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
// return TRUE if command invocation was attempted
{
……
return OnCmdMsg(nID, nCode, NULL, NULL);
}
其中调用了OnCmdMsg虚函数,虽然是在CWnd的方法中调用,但this指针指向的是一个CFrameWnd对象,所以调用的是该类中定义的OnCmdMsg。在MFC中,改写过OnCmdMsg函数的类有:
class CCmdTarget : public CObject
class CFrameWnd : public CWnd
class CMDIFrameWnd : public CFrameWnd
class CView : public CWnd
class CPropertySheet : public CWnd
class CDialog : public CWnd
class CDocument : public CCmdTarget
class COleDocument : public CDocument
很显然,我们应该转入到CFrameWnd中去探索(WINFRM.CPP中):
BOOLCFrameWnd::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;
}
该函数在处理中有三个分支线路,先后分别派发给View对象,Frame对象以及CWinApp对象。来到CView中对应的OnCmdMsg中(VIEWCORE.CPP):
BOOLCView::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 beforerouting to document
CPushRoutingView push(this);
return m_pDocument->OnCmdMsg(nID, nCode, pExtra,pHandlerInfo);
}
return FALSE;
}
函数中先后由View自身和Document来处理该命令消息。虽然字面上调用的是CWnd::OnCmdMsg,但由于CWnd并未改写OnCmdMsg函数,所以最后调用的是CCmdTarget中的OnCmdMsg函数。在CMDTARG.CPP中有:
BOOLCCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
……
//look through message map to see if it applies to us
for(pMessageMap = GetMessageMap(); pMessageMap != NULL;
pMessageMap = pMessageMap->pBaseMap)
{
lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg,nCode, nID);
if (lpEntry != NULL)
{
// found it
return_AfxDispatchCmdMsg(this,nID, nCode,
lpEntry->pfn, pExtra,lpEntry->nSig, pHandlerInfo);
}
}
return FALSE; // not handled
}
其中AfxFindMessageEntry函数已经在《MFC消息流动的内部实现》中看过了。第一个分支到此时终于走到了尽头,能做的只是比较CCmdTarget中的消息映射表。如果没有发现对应的消息处理方式,则返回FALSE,即回到CView::OnCmdMsg中,转而调用m_pDocument->OnCmdMsg。如果发现了该命令消息的对应处理方式,则调用_AfxDispatchCmdMsg函数:
AFX_STATIC BOOL AFXAPI _AfxDispatchCmdMsg(CCmdTarget* pTarget, UINT nID, int nCode,
AFX_PMSG pfn, void* pExtra, UINT nSig, AFX_CMDHANDLERINFO* pHandlerInfo)
// return TRUE to stop routing
{
ASSERT_VALID(pTarget);
UNUSED(nCode); // unused in release builds
union MessageMapFunctions mmf;
mmf.pfn = pfn;
BOOL bResult = TRUE; // default is ok
……
switch (nSig)
{
case AfxSig_vv:
// normal command or control notification
ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED
ASSERT(pExtra == NULL);
(pTarget->*mmf.pfn_COMMAND)();
break;
case AfxSig_bv:
// normal command or control notification
ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED
ASSERT(pExtra == NULL);
bResult = (pTarget->*mmf.pfn_bCOMMAND)();
break;
case AfxSig_vw:
// normal command or control notification in a range
ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED
ASSERT(pExtra == NULL);
(pTarget->*mmf.pfn_COMMAND_RANGE)(nID);
break;
case AfxSig_bw:
// extended command (passed ID, returns bContinue)
ASSERT(pExtra == NULL);
bResult = (pTarget->*mmf.pfn_COMMAND_EX)(nID);
break;
……
default: // illegal
ASSERT(FALSE);
return 0;
}
return bResult;
}
以下是没发现对应处理方式时,调用CDocument中OnCmdMsg的情况(DOCCORE.CPP):
BOOLCDocument::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和Document Template来处理相关的消息。整体上,一个FrameWnd窗口收到命令消息后,它会依次如下图的顺序尝试响应该命令消息:
为了保证类型安全,我们在最终的消息处理中看到了很多的AfxSig_(例如在_AfxDispatchCmdMsg函数和CWnd::OnWndMsg函数中)。这是因为不同的消息处理程序需要不同的参数(包括数量和类型),但是其函数指针的定义过于简陋,无法描述足够的信息。
消息映射表中的记录定义如下:
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 nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
而其中保存处理函数地址的指针AFX_PMSG定义如下:
typedef void (AFX_MSG_CALL CWinThread::*AFX_PMSGT)(void);
显然该定义无法提供我们在使用时所需的所有信息(它的参数仅仅是void类型而已),而这正是AfxSig_起作用的地方。回顾一下使用处的代码:
unionMessageMapFunctions mmf;
mmf.pfn= pfn;
switch(nSig)
{
case AfxSig_vv:
…
(pTarget->*mmf.pfn_COMMAND)();
break;
case AfxSig_bv:
…
bResult = (pTarget->*mmf.pfn_bCOMMAND)();
break;
case AfxSig_vw:
…
(pTarget->*mmf.pfn_COMMAND_RANGE)(nID);
break;
……
}
在AFXMSG_.H中定义有一个枚举变量AfxSig,如下:
enum AfxSig
{
AfxSig_end = 0, // [marks end of message map]
AfxSig_bD, // BOOL (CDC*)
AfxSig_bb, // BOOL (BOOL)
AfxSig_bWww, // BOOL (CWnd*, UINT, UINT)
AfxSig_hDWw, // HBRUSH (CDC*, CWnd*, UINT)
AfxSig_hDw, // HBRUSH (CDC*, UINT)
AfxSig_iwWw, // int (UINT, CWnd*, UINT)
AfxSig_iww, // int (UINT, UINT)
AfxSig_iWww, // int (CWnd*, UINT, UINT)
AfxSig_is, // int (LPTSTR)
AfxSig_lwl, // LRESULT (WPARAM, LPARAM)
AfxSig_lwwM, // LRESULT (UINT, UINT, CMenu*)
AfxSig_vv, // void (void)
…//后面还有很多感兴趣的话自己看文档吧
};
而至于MessageMapFunctions的部分定义如下:
union MessageMapFunctions
{
AFX_PMSG pfn; // generic member function pointer
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_D)(CDC*);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_b)(BOOL);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_u)(UINT);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_h)(HANDLE); BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_W_u_u)(CWnd*, UINT, UINT);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_W_COPYDATASTRUCT)(CWnd*, COPYDATASTRUCT*);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_HELPINFO)(LPHELPINFO);
HBRUSH (AFX_MSG_CALL CCmdTarget::*pfn_B_D_W_u)(CDC*, CWnd*, UINT);
……
};
AfxSig和MessageMapFunctions两个结构合起来,确保调用消息处理函数的时候能够接收到所需的参数信息并返回正确的值类型。在MessageMapFunctions中,其实真正的函数就只有pfn一个而已,但是通过union结构让它呈现出了多种不同的状态。不得不说,union在类型转换方面真的是有妙用!