MFC源码分析实战(五)——层层封装下的消息循环

消息循环

至此,我们还有最后一站——消息循环
为此,当程序调试运行到CWnd::CreateEx尾部时,对GetMessage/PeekMessage下条件断点,以窗口句柄为条件:
很快就断下来了:
MFC源码分析实战(五)——层层封装下的消息循环_第1张图片
在AfxWinMain中,InitInstance完成后,就开始执行Run函数:
这里写图片描述
Run又是一个虚函数,CWinApp类中override了它,因此进入CWinApp::Run:
MFC源码分析实战(五)——层层封装下的消息循环_第2张图片
在其中又调用了CWinThread::Run,其中调用了PeekMessage以PM_NONREMOVE的方式偷看消息队列,并在空闲时执行OnIdle虚函数;如果不空闲,则进入 CWinThread::PumpMessage()函数,该函数又调用了AfxInternalPumpMessage函数:

BOOL AFXAPI AfxInternalPumpMessage()
{
    _AFX_THREAD_STATE *pState = AfxGetThreadState();

    if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
    {
        return FALSE;
    }
  // process this message

    if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
    {
        ::TranslateMessage(&(pState->m_msgCur));
        ::DispatchMessage(&(pState->m_msgCur));
    }
  return TRUE;
}

基本上是一个标准的消息循环了。不过注意在GetMessage后有一个AfxPreTranslateMessage,并且可以根据其返回值决定是否翻译并分发消息(如果不分发消息的话,根本就没回到user32领空,因此AfxWndProc根本就收不到该消息)

BOOL __cdecl AfxPreTranslateMessage(MSG* pMsg)
{
  CWinThread *pThread = AfxGetThread();
  if( pThread )
    return pThread->PreTranslateMessage( pMsg );
  else
    return AfxInternalPreTranslateMessage( pMsg );
}

BOOL CWinThread::PreTranslateMessage(MSG* pMsg)
{
    ASSERT_VALID(this);
    return AfxInternalPreTranslateMessage( pMsg );
}

BOOL AfxInternalPreTranslateMessage(MSG* pMsg)
{
//    ASSERT_VALID(this);

    CWinThread *pThread = AfxGetThread();
    if( pThread )
    {
        // if this is a thread-message, short-circuit this function
        if (pMsg->hwnd == NULL && pThread->DispatchThreadMessageEx(pMsg))
            return TRUE;
    }

    // walk from target to main window
    CWnd* pMainWnd = AfxGetMainWnd();
    if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg))
        return TRUE;

    // in case of modeless dialogs, last chance route through main
    //   window's accelerator table
    if (pMainWnd != NULL)
    {
         CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
         if (pWnd->GetTopLevelParent() != pMainWnd)
            return pMainWnd->PreTranslateMessage(pMsg);
    }

    return FALSE;   // no special processing
}

这其中,最重要的是CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg)

BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)
{
    ASSERT(hWndStop == NULL || ::IsWindow(hWndStop));
    ASSERT(pMsg != NULL);

    // walk from the target window up to the hWndStop window checking
    //  if any window wants to translate this message

    for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))
    {
        CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
        if (pWnd != NULL)
        {
            // target window is a C++ window
            if (pWnd->PreTranslateMessage(pMsg))
                return TRUE; // trapped by target window (eg: accelerators)
        }

        // got to hWndStop window without interest
        if (hWnd == hWndStop)
            break;
    }
    return FALSE;       // no special processing
}

其从消息所属窗口开始,到其父窗口,一直到主窗口,以此调用其PreTranslateMessage,直到找到一个返回TRUE的,就返回TRUE,大家都不处理,则返回FALSE。这里,从hWnd到CWND*的转换又是通过CWnd::FromHandlePermanent完成的。

CWnd::OnWndMsg中的小宇宙

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
    LRESULT lResult = 0;
    union MessageMapFunctions mmf;
    mmf.pfn = 0;
    CInternalGlobalLock winMsgLock;
    // 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;
    }

    // special case for activation
    if (message == WM_ACTIVATE)
        _AfxHandleActivate(this, wParam, CWnd::FromHandle((HWND)lParam));

    // special case for set cursor HTERROR
    if (message == WM_SETCURSOR &&
        _AfxHandleSetCursor(this, (short)LOWORD(lParam), HIWORD(lParam)))
    {
        lResult = 1;
        goto LReturnTrue;
    }

   // special case for windows that contain windowless ActiveX controls
   BOOL bHandled;

   bHandled = FALSE;
   if ((m_pCtrlCont != NULL) && (m_pCtrlCont->m_nWindowlessControls > 0))
   {
      if (((message >= WM_MOUSEFIRST) && (message <= AFX_WM_MOUSELAST)) ||
         ((message >= WM_KEYFIRST) && (message <= WM_IME_KEYLAST)) ||
         ((message >= WM_IME_SETCONTEXT) && (message <= WM_IME_KEYUP)))
      {
         bHandled = m_pCtrlCont->HandleWindowlessMessage(message, wParam, lParam, &lResult);
      }
   }
   if (bHandled)
   {
      goto LReturnTrue;
   }

    //否则是普通消息,垂直向上传递即可,具体代码见上

其中我们可以看到,ON_COMMAND消息被区别对待了,在CWnd类中,有一个名为OnCommand的虚函数专门处理此类消息。而CFrameWnd中重载了该函数,CView类中没有重载。因此

CFrameWnd类的对象,调用的时CFrameWnd::OnCommand,该函数沿CFrameWnd的消息映射族谱,按正常消息向上查找

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

该类中对帮助按钮所发出的COMMAND消息进行了特别处理,如果不是此类消息,则调用CWnd::OnCommand
CView类并未实现OnCommand方法,故与CWnd类的对象一样,调用的是CWnd::OnCommand。这样,除了CFrameWnd中的特殊帮助类消息外,所有ON_COMMAND消息又殊途同归了。

BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
    // return TRUE if command invocation was attempted
{
    UINT nID = LOWORD(wParam);
    HWND hWndCtrl = (HWND)lParam;
    int nCode = HIWORD(wParam);

    // default routing for command messages (through closure table)

    if (hWndCtrl == NULL)
    {
        //特殊处理句柄为NULL的
    }
    else
    {
        // control notification
        ASSERT(nID == 0 || ::IsWindow(hWndCtrl));

        if (_afxThreadState->m_hLockoutNotifyWindow == m_hWnd)
            return TRUE;        // locked out - ignore control notification

        // reflect notification to child window control
        if (ReflectLastMsg(hWndCtrl))
            return TRUE;    // eaten by child

        // zero IDs for normal commands are not allowed
        if (nID == 0)
            return FALSE;
    }
    return OnCmdMsg(nID, nCode, NULL, NULL);
}

关键是在最后调用了虚函数OnCmdMsg,不同类型不同的处理函数。
先看看CFrameWnd::OnCmdMsg

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

可以看出,框架类的OnCmdMsg依次调用了CView::OnCmdMsg、CWnd::OnCmdMsg(未override,为CCmdTarget::OnCmdMsg)
而CView::OnCmdMsg又依次调用CWnd::OnCmdMsg(未override,为CCmdTarget::OnCmdMsg)和CDocument::OnCmdMsg。其中CCmdTarget::OnCmdMsg是从CMyView->CView->CWnd->CCmdTarget的顺序找其消息映射表。
如果没找到的话,就返回,由CDocument::OnCmdMsg继续查找:CMyDoc->CDocument->CCmdTarget
如果还是没找到的话,CView::OnCmdMsg返回FALSE,由CWnd::OnCmdMsg(CCmdTarget::OnCmdMsg)接手:
CMyFrameWnd->CFrameWnd->CWnd->CCmdTarget
如果仍然没有找到,则由pApp->OnCmdMsg接手:
CMyWinApp->CWinApp->CCmdTarget
全剧终~

你可能感兴趣的:(MFC源码分析实战(五)——层层封装下的消息循环)