一、环境
IDE:VC6.0
OS:WindowsXp
二、编写测试代码和环境配置
新建一个Win32 Application 选择 “A Simple Win32 Application”
打开stdafx.h头文件把
#include <windows.h>
更改为:
#include <afxwin.h>
修改工程设置使用MFC静态库以便能够查看微软提供的MFC源代码
Project->Settings->MicrosoftFoundation classes:Use MFC in a Static Library
回到工程cpp文件中编写如下测试代码:
#include "stdafx.h" #include "resource.h" class CMyView : public CView { DECLARE_MESSAGE_MAP () public: virtual void OnDraw (CDC* pDC); afx_msg void OnFileNew (); }; BEGIN_MESSAGE_MAP (CMyView, CView) //ON_COMMAND (ID_NEW, OnFileNew) END_MESSAGE_MAP () void CMyView::OnDraw (CDC* pDC) { pDC->Ellipse (100, 100, 400, 400); } void CMyView::OnFileNew () { AfxMessageBox ("CMyView::OnFileNew"); } class CMyFrameWnd : public CFrameWnd { DECLARE_MESSAGE_MAP () public: afx_msg int OnCreate (LPCREATESTRUCT lpCreateStruct); afx_msg void OnFileNew (); }; BEGIN_MESSAGE_MAP (CMyFrameWnd, CFrameWnd) ON_WM_CREATE () //ON_COMMAND (ID_NEW, OnFileNew) END_MESSAGE_MAP () int CMyFrameWnd::OnCreate (LPCREATESTRUCT lpCreateStruct) { CMyView *pView = new CMyView; pView->Create (NULL, "", WS_CHILD | WS_VISIBLE | WS_BORDER, CRect (0,0,100,100), this, /*1001*/AFX_IDW_PANE_FIRST); m_pViewActive = pView; return CFrameWnd::OnCreate (lpCreateStruct); } void CMyFrameWnd::OnFileNew () { AfxMessageBox ("CMyFrameWnd::OnFileNew"); } class CMyWinApp : public CWinApp { DECLARE_MESSAGE_MAP () public: virtual BOOL InitInstance (); afx_msg void OnNewFile (); }; BEGIN_MESSAGE_MAP (CMyWinApp, CWinApp) ON_COMMAND (ID_NEW, OnNewFile) END_MESSAGE_MAP () void CMyWinApp::OnNewFile () { AfxMessageBox ("CMyWinApp::OnNewFile"); } CMyWinApp theApp; BOOL CMyWinApp::InitInstance () { CMyFrameWnd *pFrame = new CMyFrameWnd; pFrame->Create (NULL, "MFCSplit", WS_OVERLAPPEDWINDOW, CFrameWnd::rectDefault, NULL, MAKEINTRESOURCE(IDR_MENU1)); m_pMainWnd = pFrame; pFrame->ShowWindow (SW_SHOW); pFrame->UpdateWindow (); return TRUE; }
注:工程中还需要添加一个菜单资源,菜单只需一个下来菜单项,ID更改为ID_NEW,名字这里为:FileNew
上面的代码值得注意的是:创建视图对象以及窗口应该在框架窗口的OnCreate虚函数中完成,并且把创建的视图对象赋值给框架窗口的m_pViewActive以便程序已启动视图窗口就处于活动状态(并且建立了视图窗口和框架窗口的联系)。
另外:创建的视图窗口时指定了父窗口为框架窗口(this),这样结合在应用程序InitInstance函数中(m_pMainWnd = pFrame)的赋值可以总结如下对象的结构图
对象结构图:
m_pMainWnd =pFarame
m_pViewActive =pView
CMyWinApptheApp;//全局对象,随用随取
另外:AfxGetApp () 、AfxGetThread()均可放回theApp对象地址
theApp
|----->m_pMainWnd
|----------->m_pViewActice
三、断点跟踪分析
上面的代码中:视图类、框架类、窗口类我都添加了响应菜单的消息映射宏,如果想简单验证结论的话分别注释其中的任意一个测试即可得出如下命令的路由顺序:
视图类-à框架类--à应用程序类
当然这里我要从微软提供的源代码中一探究竟: 注释视图类和窗口类中的命令消息响应宏
在CMyView:;OnFileNew的入口处下断,F5调试启动程序,点击我们的FileNew菜单触发消息,那我我们就来到了断点处
在VC的工具栏:View-->DebugWindows-->CallStack可以看到CMyFileView::OnFileNew的调用堆栈,双击AfxWndProc进入该函数的入口处设置断点
停止当前调试,重新F5调试启动程序,来到AfxWndProc入口的断点处
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) {// 参数hWnd保存了消息产生的窗口句柄 ...................................... // 通过hWnd找到与之关联的类对象 CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); ....................................... return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam); }
跟
AfxWndProc->AfxCallWndProc
进
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0) {// pWnd为产生消息的对象pFrame ................................... // 利用pFrame调用WinddowProc,WindowProc为虚函数可以重写 // 而调用我们定义的窗口处理函数 lResult = pWnd->WindowProc(nMsg, wParam, lParam); }
跟
AfxWndProc--> AfxCallWndProc-->WindowProc
进
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {// this==pFrame .................................................. if (!OnWndMsg(message, wParam, lParam, &lResult)) lResult = DefWindowProc(message, wParam, lParam); return lResult; }
跟
AfxWndProc-->AfxCallWndProc--> WindowProc-->OnWndMsg
进
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult) {// this===pFarme ........................................... // 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; } .......................................................... }
在OnWndMsg中的if (message == WM_COMMAND)命令消息的处理分支中设置断点按F5运行并触发消息运行到该断点
跟
AfxWndProc-->AfxCallWndProc-->WindowProc-->OnWndMsg-->OnCommand
进
BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam) {// this===pFrame .................................... // route as normal command return CWnd::OnCommand(wParam, lParam); }
跟
AfxWndProc-->AfxCallWndProc-->WindowProc-->OnWndMsg-->OnCommand-->OnCommand
进
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam) {// this===pFrame ........................................ return OnCmdMsg(nID, nCode, NULL, NULL); }
跟
AfxWndProc-->AfxCallWndProc-->WindowProc--> OnWndMsg-->OnCommand-->OnCommand-->OnCmdMsg
进
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) {// this==pFrame .................................... // 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; }
在这个函数中可以清晰的看到命令消息的路由处理过程,CView-->CFrameWnd-->theApp
关于消息的查找过程前面的一片博文已经分析过了这里不再赘述,大概绘制如下消息映射查找链表如下:
那么这里就仅对theApp的消息处理过程做分析
跟
AfxWndProc--> AfxCallWndProc--> WindowProc--> OnWndMsg-->OnCommand-->OnCmdMsg
进
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) {// this===pView for (pMessageMap = GetMessageMap(); pMessageMap != NULL;pMessageMap = pMessageMap->pBaseMap) {// pMessageMap = GetMessageMap()拿到消息映射链表的头结点也即theApp对象中的静态成员messageMap的地址 // 到_messageEntries数组中查找和包含消息对应的处理函数的元素,若找到lpEntry则指向该元素否则为空继续查找 const AFX_MSGMAP_ENTRY* lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID); ....................................... // 如果查找到直接派发调用该消息对象对应的处理函数 return _AfxDispatchCmdMsg(this, nID, nCode,lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo); } ......................................................... }
好了,到这里命令消息的路由过程已经分析完毕了
pView-->pFarme-->theApp