MFC消息映射深入挖掘2

上篇已经描述了消息映射表的原理,下面主要说说MFC如何利用这张表来进行消息映射。

 

在MFC中,分为三种消息,三种消息的传递路线是不同的,可以参看一下代码:

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)

{

   ...

    if (message == WM_COMMAND)

    {

       if (OnCommand(wParam, lParam));

     }

   ...

    if (message == WM_NOTIFY)

    {

       if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))

    }

   //以下是标准消息的处理

    ...

}

以上是摘自wincore.cpp中,OnWndMsg是用来分辨并处理消息的专职机构。

如果是命令消息,就交给OnCommand处理,如果是通知消息,就交给OnNotify处理。 标准消息则严格按照继承的路线走。

 

标准消息是按照消息映射表的线路来走的,举个例子:

CMyView->CView->CWnd->CCmdTarget,这是一个继承关系,标准消息按照这个路线,从子类走到父类,中途一旦搜索到有相应的消息处理函数,即处理。

 

 

而命令消息则要拐个弯,上面已经看到,如果是命令消息,就交给OnCommand处理。

因为命令消息只有CWnd及其派生类才能接收到,所以,这里的OnCommand不一定是CWnd的OnCommand,也有可能是其子类的OnCommand,根据多态性原理,如果其子类改写了OnCommand,则调用其子类的OnCommand。

在MFC中,以下数个类都改写了OnCommand虚函数:

CWnd;CFrameWnd;CMDIFrameWnd;CSplitterWnd;CPropertySheet;COlePropertyPage;

 

举个具体的示例吧,假设消息从CFrameWnd进来:

//in winfrm.cpp

BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)

{

     ....

     return CWnd::OnCommand(wParam, lParam);

}

很明显,调用了CWnd::OnCommand:

//in wincore.cpp

BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)

{

    ....

    return OnCmdMsg(nID, nCode, NULL, NULL);

}

这里调用的OnCmdMsg不一定是CWnd的,根据多态性原理,此处很明显是调用了CFrameWnd::OnCmdMsg:

BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)

{

    ....

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

}

在这里,有三个分支,很明显,传递的路线应该是:

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之后,先调用CWnd::OnCmdMsg,这里因为CWnd没有改写OnCmdMsg,其实就是调用了,CCmdTarget::OnCmdMsg:

//cmdtarg.cpp

BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)

{

 for (pMessageMap = GetMessageMap(); pMessageMap->pfnGetBaseMap != NULL;pMessageMap = (*pMessageMap->pfnGetBaseMap)())
 {

     ....

    lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);
    if (lpEntry != NULL)
    {

       ....
       return _AfxDispatchCmdMsg(this, nID, nCode,lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);
    }

 }

return FALSE;

}

可以看到,这个时候开始比较CCmdTarget的消息映射表,如果没有吻合者,则返回FALSE。好了,可以回到CView::OnCmdMsg中继续执行了,在CView中,下面是调用m_pDocument->OnCmdMsg:

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

到了CDocument,也是先调用CCmdTarget::OnCmdMsg,如找到相应的消息函数,则传回FALSE。接下来调用m_pDocTemplate->OnCmdMsg,若未找到相应的处理函数,则返回FALSE。

 

好了,这时候,CView::OnCmdMsg返回FALSE,回到CFrameWnd::OnCmdMsg继续往下执行。

 

总结一下:
OnCmdMsg函数,是专门用来对付命令消息的函数,每一个“可接受命令消息的对象”在处理消息时,都应该调用另一个目标类的OnCmdMsg,这样才能将消息传递下去。

 

总的传递路径就是:

Frame窗口接收到消息->传递给view(未处理)->传递给document(未处理)->传递给document template(未处理)->返回给Frame窗口(未处理)->CWinApp对象(调用::DefWindowProc)

 

下面举一个具体的例子:

 

假设现在有一个sdi风格的程序,我们在菜单上选择了某项,ID为IDM_TEST,其相应程序在doc类下。消息产生了,路线如下:

1、CMyFrameWnd主窗口收到命令消息

2、CMyFrameWnd给它自己的子窗口CMyView一个机会,结果CView检查自己的messagemap,发现自己没有相应的消息处理函数。

3、CMyView只能将消息传递给CMyDocument,CMyDocument在自己的messagemap中发现了一个吻合项,于是调用响应函数,消息流动终止了。

4、假设CMyDocument没有这个消息的处理函数,则消息继续传递,CMyDocument会把消息传递到Document Template对象去。

5、若还是没被处理,则消息回到CMyView。

6、CMyView未处理,则又回到CMyFrameWnd主窗口。

7、CMyFrameWnd传递消息给CWinApp,最终调用::DefWindowProc处理无主消息。

到此为止,还有一个未弄明白的地方。那就是消息映射网中的_messageEntries[]的数组内容。为什么消息经由映射,可以找到它的处理函数呢?

上一章提到,messagemap中,每一笔记录是这样的:

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

而用用于实例化的ON_宏,是这样的:

#define ON_WM_CREATE() /
 { WM_CREATE, 0, 0, 0, AfxSig_is, /
  (AFX_PMSG) (AFX_PMSGW) /
  (static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > ( &ThisClass :: OnCreate))
},

用黑体字标示的是参数AFX_PMSG pfn,一个可怕的类型转换,这么处理时为了保证类型安全(typed-safe),实际上是一个指向消息响应函数的指针。

 

有一个很奇怪的东西, AfxSig,这个东西的作用是向消息响应函数中,传递参数。来看看它的实现。

以下代码可以在CWnd::OnWndMsg中找到(wincore.cpp)

union MessageMapFunctions mmf;//共用体类型

mmf.pfn = lpEntry->pfn;

switch (lpEntry->nSig)

{

    ...

  case AfxSig_i_v_s:
  lResult = (this->*mmf.pfn_i_s)(reinterpret_cast<LPTSTR>(lParam));
  break;

    ...

}

注意:这里显示的是AfxSig_i_v_s而不是AfxSig_is,这是因为在vs2008中,AfxSig_is被视为旧的版本,MFC9.0中已经用AfxSig_i_v_s替代,可以在afxmsg_.h中找到以下代码:

// Old

...

AfxSig_is = AfxSig_i_v_s,      // int (LPTSTR)

 

其中MessageMapFunctions的定义在afximpl.h中

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

 int (AFX_MSG_CALL CWnd::*pfn_i_s)(LPTSTR);

 ...

}

其实真正的函数只有一个pfn,但通过union,他拥有了许多不同的形象,如pfn_i_s代表了“返回值为int,参数为LPRSTR字符串”。相当繁琐的一个过程,但尤为精妙。

 

 

好了,最后总结一下:

1、MFC中消息分为命令消息、标准消息、控件通知消息。

2、消息传递是通过消息映射表格,此表格的声明利用BEGIN_MESSAGE_MAP/ON_**/END_MESSAGE_MAP三个宏。

3、标准消息的流动路线按照继承线路走。命令消息则是按照特殊路线走。

 

你可能感兴趣的:(MFC消息映射深入挖掘2)