MFC 消息映射(一)
---恢复内容开始---
最近在看《深入浅出MFC》一书。消息映射流程图如下:
在此附加上不实用MFC Wizard 编写的简单MFC窗口程序,纯粹为学习消息映射。下载地址:Hello.06。
MFC 程序入口:
1 int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,_In_ LPTSTR lpCmdLine, int nCmdShow) 2 { 3 4 CWinApp* pApp = AfxGetApp(); 5 6 // AFX internal initialization 7 AfxWinInit(hInstance, hPrevInstance, lpCmdLine,nCmdShow) 8 9 pApp->InitApplication()); 10 11 // Perform specific initializations 12 pThread->InitInstance(); 13 14 nReturnCode = pThread->Run(); 15 16 return nReturnCode; 17 }
pThread->InitInstance() 即调用 :(Note:CMyWinAPP 改下了CWinApp 的InitInstance()函数,根据C++多态原则,调用)
1 BOOL CMyWinApp::InitInstance() 2 { 3 m_pMainWnd = new CMyFrameWnd(); 4 m_pMainWnd->ShowWindow(m_nCmdShow); 5 m_pMainWnd->UpdateWindow(); 6 return TRUE; 7 } 8 9 //-------------------------------------------------------------------- 10 // CMyFrameWnd's member 11 //-------------------------------------------------------------------- 12 CMyFrameWnd::CMyFrameWnd() 13 { 14 Create(NULL, "Hello MFC", WS_OVERLAPPEDWINDOW, rectDefault, 15 NULL, "MainMenu"); // "MainMenu" 定義於 RC 檔 16 }
上述代码的CWnd::Create()完成了窗口类的注册(Register)并绑定窗口函数:
LRESULT CALLBACK
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam),
然后创建窗口(CreateWindow) 。
AfxWndProc函数即对应文首图中函数。
利用hook 技术,任何默认创建的窗口都以AfxWndProc作为窗口函数。
下图介绍了CFrameWnd,CDocument,CView之间的消息路由顺序:
相关原始代码如下:
1 BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, 2 AFX_CMDHANDLERINFO* pHandlerInfo) 3 { 4 CPushRoutingFrame push(this); 5 6 // pump through current view FIRST 7 CView* pView = GetActiveView(); 8 if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 9 return TRUE; 10 11 // then pump through frame 12 if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 13 return TRUE; 14 15 // last but not least, pump through app 16 CWinApp* pApp = AfxGetApp(); 17 if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 18 return TRUE; 19 20 return FALSE; 21 } 22 23 ///////////////////////////////////////////////////////////////////////////// 24 // Command routing 25 26 BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra, 27 AFX_CMDHANDLERINFO* pHandlerInfo) 28 { 29 // first pump through pane 30 if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 31 return TRUE; 32 33 // then pump through document 34 if (m_pDocument != NULL) 35 { 36 // special state for saving view before routing to document 37 CPushRoutingView push(this); 38 return m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); 39 } 40 41 return FALSE; 42 } 43 CMDIChildWnd::OnCmdMsg 44 BOOL CMDIFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, 45 AFX_CMDHANDLERINFO* pHandlerInfo) 46 { 47 CMDIChildWnd* pActiveChild = MDIGetActive(); 48 // pump through active child FIRST 49 if (pActiveChild != NULL) 50 { 51 CPushRoutingFrame push(this); 52 if (pActiveChild->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 53 return TRUE; 54 } 55 56 // then pump through normal frame 57 return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); 58 } 59 60 61 62 ///////////////////////////////////////////////////////////////////////////// 63 // command routing 64 65 BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra, 66 AFX_CMDHANDLERINFO* pHandlerInfo) 67 { 68 if (CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 69 return TRUE; 70 71 // otherwise check template 72 if (m_pDocTemplate != NULL && 73 m_pDocTemplate->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 74 return TRUE; 75 76 return FALSE; 77 } 78 ///////////////////////////////////////////////////////////////////////////// 79 80 81 82 BOOL CDocTemplate::OnCmdMsg(UINT nID, int nCode, void* pExtra, 83 AFX_CMDHANDLERINFO* pHandlerInfo) 84 { 85 BOOL bReturn; 86 CCmdTarget* pFactory = DYNAMIC_DOWNCAST(CCmdTarget, m_pAttachedFactory); 87 88 if (nCode == CN_OLE_UNREGISTER && pFactory != NULL) 89 bReturn = pFactory->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); 90 else 91 bReturn = CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); 92 93 return bReturn; 94 } 95 96 BOOL CDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra, 97 AFX_CMDHANDLERINFO* pHandlerInfo) 98 { 99 if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 100 return TRUE; 101 102 if ((nCode != CN_COMMAND && nCode != CN_UPDATE_COMMAND_UI) || 103 !IS_COMMAND_ID(nID) || nID >= 0xf000) 104 { 105 // control notification or non-command button or system command 106 return FALSE; // not routed any further 107 } 108 109 // if we have an owner window, give it second crack 110 CWnd* pOwner = GetParent(); 111 if (pOwner != NULL) 112 { 113 TRACE(traceCmdRouting, 1, "Routing command id 0x%04X to owner window.\n", nID); 114 115 ASSERT(pOwner != this); 116 if (pOwner->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 117 return TRUE; 118 } 119 120 // last crack goes to the current CWinThread object 121 CWinThread* pThread = AfxGetThread(); 122 if (pThread != NULL) 123 { 124 TRACE(traceCmdRouting, 1, "Routing command id 0x%04X to app.\n", nID); 125 126 if (pThread->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 127 return TRUE; 128 } 129 130 TRACE(traceCmdRouting, 1, "IGNORING command id 0x%04X sent to %hs dialog.\n", nID, 131 GetRuntimeClass()->m_lpszClassName); 132 133 return FALSE; 134 }
在 OnCmdMsg过程中,消息对应的函数有_AfxDispatchCmdMsg调用。
CCmdTarget::OnCmdMsg函数很关键,原始代码摘抄下来:
1 BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra, 2 AFX_CMDHANDLERINFO* pHandlerInfo) 3 { 4 //在【消息:On函数】表中查找,找不到就到父类查找 5 // for backward compatibility HIWORD(nCode)==0 is WM_COMMAND 6 if (nMsg == 0) 7 nMsg = WM_COMMAND; 8 9 // look through message map to see if it applies to us 10 11 for (pMessageMap = GetMessageMap(); pMessageMap->pfnGetBaseMap != NULL; 12 pMessageMap = (*pMessageMap->pfnGetBaseMap)()) 13 { 14 // Note: catches BEGIN_MESSAGE_MAP(CMyClass, CMyClass)! 15 ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)()); 16 lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID); 17 if (lpEntry != NULL) 18 { 19 // found it 20 #ifdef _DEBUG 21 if (nCode == CN_COMMAND) 22 TRACE(traceCmdRouting, 1, "SENDING command id 0x%04X to %hs target.\n", nID, 23 GetRuntimeClass()->m_lpszClassName); 24 else if (nCode > CN_COMMAND) 25 TRACE(traceCmdRouting, 1, "SENDING control notification %d from control id 0x%04X to %hs window.\n", 26 nCode, nID, GetRuntimeClass()->m_lpszClassName); 27 #endif //_DEBUG 28 return _AfxDispatchCmdMsg(this, nID, nCode, 29 lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo); 30 } 31 } 32 return FALSE; // not handled 33 }
下图是CView类的 WM_PAINT消息路由过程:
上图也说明了MFC 文档/视结构的继承关系。
总结:C++多态可以从数据与函数的角度理解,数据属于具体的类实例,一个实例一份;成员函数代码确为多个实例共享。虚函数表由
项【实例指针:重载函数】组成,其实现了“子类没有才调用父类虚函数”功能。
另外对于形如的代码:
1 BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, 2 AFX_CMDHANDLERINFO* pHandlerInfo) 3 { 4 CPushRoutingFrame push(this); 5 6 // pump through current view FIRST 7 CView* pView = GetActiveView(); 8 if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 9 return TRUE; 10 11 // then pump through frame 12 if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 13 return TRUE; 14 15 // last but not least, pump through app 16 CWinApp* pApp = AfxGetApp(); 17 if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) 18 return TRUE; 19 20 return FALSE; 21 }
在子类的重载函数中使用Parent::Fun方式调用父类函数可以简单理解为从父类复制相关代码。还要注意的是子类继承父类函数也有类似特征(复制)。
boyang987
---恢复内容结束---