我写的每一遍文章都是有准备和有原因的,我绝不是凭个人的心情去做这件事。
Windows消息的传递路线是MFC最神秘的地带之一,MFC的这种机制曾经困扰着无数的编程爱好者。长期以来,我也被它所困扰,而且让我很不爽。当然,很多前辈都对此有过比较深入的剖析,我也看过很多,但是我天生愚钝,看过各种版本任然不能彻底明白其中的机制到底是怎么样实现的。随着我对MFC的理解更加深入,我终于理清了它的头绪。当然,在这个时代还去分析MFC的内部机制是很愚蠢的,因为肯定有些人会站出来跟我说:你傻了,MFC是什么时候的东西啊,你现在还在纠结它,闲得蛋疼吧。但是我还是想搞清楚MFC的这种机制,我相信我的这篇文章在某个时候会帮到一些人,即使不能,我也不会感到遗憾,我尽力了。
即使你刚开始学习MFC,那也没关系,我相信你仍能从下面的文字和代码中有所收获。其中,我会贴出关键的MFC底层代码,这样更有利于我们的理解。
分析流程如下:
1.分析Windows消息在MFC中的总体传递路线;
2.分析Window命令消息WM_COMMAND的传递路线。
一.Windows消息在MFC中的总体传递路线
传递路线:AfxWndProc-----AfxCallWndProc-----WindowProc-----OnWndMsg-----DefWindowProc-----::DefWindowProc。AfxWndProc是消息的起点,为什么呢?请看MFC源代码:
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
......
AfxHookWindowCreate(this);
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
}
//安装hook的函数
void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
.......
pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
......
}
//HookPorc钩子过程
LRESULT CALLBACK_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
{
......
WNDPROC afxWndProc = AfxGetAfxWndProc();
oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC,(DWORD)afxWndProc);
......
return lResult;
}
WNDPROC AFXAPI AfxGetAfxWndProc()
{
......
return &AfxWndProc;
}
从上面的代码中我们可以知道,在创建窗口之前,MFC安装了一个WH_CBT类型的钩子。在每个窗口对象产生之际,将窗口所属类的窗口函数替换为AfxWndProc,::DispatchMessage就把消息都传送到AfxWndProc中去了。所以,消息的起点是AfxWndProc。继续看MFC中的源代码:
//消息的起点
LRESULT CALLBACK
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
......
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
ASSERT(pWnd != NULL);
ASSERT(pWnd->m_hWnd == hWnd);
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
WPARAM wParam = 0, LPARAM lParam = 0)
{
......
lResult = pWnd->WindowProc(nMsg, wParam, lParam);
......
return lResult;
}
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = 0;
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}
LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)
{
......
return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam);
......
}
分析上面的代码,MFC中的消息沿着上面的路线走就顺理成章了。
那么消息到底在哪儿被处理的?如何被处理的呢?请看MFC源代码:
//大部分消息在这个函数中处理
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam,
LRESULT* pResult) {
if (message == WM_COMMAND) { if (OnCommand(wParam, lParam)) {
...... } return FALSE; }
if (message == WM_NOTIFY) {
...... if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult)) return FALSE; }
//比较消息映射表
const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap(); UINT iHash; iHash = (LOWORD((DWORD)pMessageMap) ^ message) &
(iHashMax-1); AfxLockGlobals(CRIT_WINMSGCACHE); AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash]; const AFX_MSGMAP_ENTRY* lpEntry;
......
}
Windows消息分为三类:命令消息WM_COMMAND,控件的通告消息WM_NOTIFY,标准消息WM_XXX。MFC中的消息经过此路线一步一步传递,到达OnWndMsg,如果消息是命令消息WM_COMMAND,则交由虚函数OnCommand处理;如果是通告消WM_NOTIFY,则交由虚函数OnNotify处理;如果是标准消息WM_XXX,就比较消息映射表Message Map,找到消息处理函数。
二.WM_COMMAND消息在MFC中的传递路线
WM_COMMAND消息交由OnCommand这个函数处理,我们假设消息是从CFrameWnd进来为例子来分析WM_COMMAND消息的传递路线。
首先看MFC中的源代码:
BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
......
return CWnd::OnCommand(wParam, lParam);
}
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
......
return OnCmdMsg(nID, nCode, NULL, NULL);
}
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
CWinApp* pApp = AfxGetApp();
if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
return FALSE;
}
BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)
{
if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))//其实就是调用CCmdTarget::OnCmdMsg
return TRUE;
if (m_pDocument != NULL)
{
......
return m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
return FALSE;
}
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO*pHandlerInfo)
{
......
for (pMessageMap = GetMessageMap(); pMessageMap != NULL;
pMessageMap = pMessageMap->pBaseMap)
{
ASSERT(pMessageMap != pMessageMap->pBaseMap);
lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);
if (lpEntry != NULL)
{
return _AfxDispatchCmdMsg(this, nID, nCode,
lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);
}
......
}
从上面的代码可以看到,当消息WM_COMMAND从CFrameWnd进来时消息传递路径如下:View----Document----FrameWnd--WinApp.