该示例通过研究基本的单文档程序的“文件”--“打开”命令,分析WM_COMMAND消息投递流程。基于VS 2005 代码
AfxWndProc最终调用的是OnWndMsg,这个函数负责消息的分发处理。当消息是WM_COMMAND时,将消息投递给OnCommand函数。
// wincore.cpp 1746 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; } //... }
OnCommand是个虚函数,因为消息是主窗口产生的,所以调用的是CFrameWnd::OnCommand函数该函数先检查该消息是不是在线请求帮助,如果是,则程序给框架窗口发送一个WM_COMMANDHELP消息,否则交由基类CWnd::OnCommand()处理。
// winfrm.cpp 299 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); }
CWnd::OnCommand首先检查表示控件的LPARAM。如果消息是由控件产生的,LPARAM就会包含控件窗口句柄。如果消息是控件通知,框架就会执行特定的处理过程。如果消息是为某个控件产生的,OnCommand会将消息直接发送给控件,然后OnCommand返回。否则,CWnd::OnCommand()要确保产生命令的界面元素没有被禁用,然后将消息传递给OnCmdMsg函数,调用的是CFrameWnd::OnCmdMsg函数
// wParam // The high-order word specifies the notification code if the message is from a control. // If the message is from an accelerator, this value is 1. If the message is from a menu, // this value is zero. // The low-order word specifies the identifier of the menu item, control, or accelerator. // lParam // Handle to the control sending the message if the message is from a control. // Otherwise, this parameter is NULL. 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) { // 菜单命令先走这里 // zero IDs for normal commands are not allowed if (nID == 0) return FALSE; // make sure command has not become disabled before routing CTestCmdUI state; state.m_nID = nID; OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL); // CFrameWnd::OnCmdMsg if (!state.m_bEnabled) { TRACE(traceAppMsg, 0, "Warning: not executing disabled command %d/n", nID); return TRUE; } // menu or accelerator nCode = CN_COMMAND; } else { // ToolBar命令走这里,或控件通知 // 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; } // 调用CFrameWnd::OnCmdMsg return OnCmdMsg(nID, nCode, NULL, NULL); }
CFrameWnd::OnCmdMsg函数调用时,pExtra和pHandlerInfo是NULL,因为处理命令不需要这一信息。取出的消息按照以下顺序经过应用程序的各个部分:活动视图、活动视图的文档、文档模板、主窗口、应用程序。
// winfrm.cpp 880 BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { CPushRoutingFrame push(this); // 将消息传递给活动视图,调用CView::OnCmdMsg CView* pView = GetActiveView(); if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // 由框架处理该消息,CWnd没有覆盖OnCmdMsg,调用CCmdTarget::OnCmdMsg if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // 将消息传递给应用程序,CWinApp没有覆盖OnCmdMsg,所以调用CCmdTarget::OnCmdTarget CWinApp* pApp = AfxGetApp(); if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; return FALSE; } // viewcore.cpp 154 BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // 首先由活动视图处理该消息,调用CCmdTarget::OnCmdMsg if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // then pump through document if (m_pDocument != NULL) { // 将该消息传递给视图对应的文档,调用CDocument::OnCmdMsg CPushRoutingView push(this); return m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); } return FALSE; } // doccore.cpp 834 BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { if (CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // otherwise check template,调用CDocTemplate::OnCmdMsg if (m_pDocTemplate != NULL && m_pDocTemplate->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; return FALSE; } // doctempl.cpp 370 BOOL CDocTemplate::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { BOOL bReturn; CCmdTarget* pFactory = DYNAMIC_DOWNCAST(CCmdTarget, m_pAttachedFactory); if (nCode == CN_OLE_UNREGISTER && pFactory != NULL) bReturn = pFactory->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); else bReturn = CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); return bReturn; }
上面的一些函数确定了程序执行流程,最终会调用到CCmdTarget::OnCmdMsg,该函数通过查找消息映射表,进而调用到消息处理函数。
// cmdtarg.cpp 297 BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // ... // determine the message number and code (packed into nCode) const AFX_MSGMAP* pMessageMap; const AFX_MSGMAP_ENTRY* lpEntry; UINT nMsg = 0; //... if (nCode != CN_UPDATE_COMMAND_UI) { nMsg = HIWORD(nCode); nCode = LOWORD(nCode); } //... // for backward compatibility HIWORD(nCode)==0 is WM_COMMAND if (nMsg == 0) nMsg = WM_COMMAND; // look through message map to see if it applies to us for (pMessageMap = GetMessageMap(); pMessageMap->pfnGetBaseMap != NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)()) { // Note: catches BEGIN_MESSAGE_MAP(CMyClass, CMyClass)! ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)()); 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 }