MFC命令传递的内部流程

我们在《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在类型转换方面真的是有妙用!



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值