MFC源码分析实战(五)——层层封装下的消息循环
2018-01-31 17:40
531 查看
消息循环
至此,我们还有最后一站——消息循环为此,当程序调试运行到CWnd::CreateEx尾部时,对GetMessage/PeekMessage下条件断点,以窗口句柄为条件:
很快就断下来了:
在AfxWinMain中,InitInstance完成后,就开始执行Run函数:
Run又是一个虚函数,CWinApp类中override了它,因此进入CWinApp::Run:
在其中又调用了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源码实战分析(三)——消息映射原理与消 4000 息路由机制初探
- Android消息循环机制源码分析
- Android 消息循环机制源码分析
- Android应用程序消息循环源码分析
- MFC消息循环和消息泵的分析
- MFC消息封装分析---Windows系列1
- SpringMVC4.x源码分析(七):使用XStream处理xml请求和响应消息实战
- Android应用程序消息循环源码分析
- Android SurfaceFlinger服务的消息循环过程源码分析
- Android SurfaceFlinger服务的消息循环过程源码分析
- MFC、ATL窗口消息封装机制对比分析
- 分析:由 XToolTip类 联想到 WTL (MFC) 消息循环
- MFC源码实战分析(四)——hWnd与CWnd之千里情缘一线牵
- Android 消息循环机制源码分析
- Android 消息循环机制源码分析
- Android 消息循环机制源码分析
- android 消息循环的源码分析
- zookeeper实战与源码分析----用ZooKeeper封装自己的客户端工具
- android的消息处理机制(图+源码分析)——Looper,Handler,Message
- libevent高性能网络库源码分析——事件循环(五)