对于上面的过程我们可能会对设置的消息处理函数有疑问,如果创建的窗口的消息处理函数是Windows默认的消息处理,那么MFC如何对各种消息进行响应呢?对于这个问题我们回到CWnd::CreateEx函数中,我们可以看到在CFrameWnd::PreCreateWindow函数调用之后有会调用AfxHookWindowCreate这个函数,进入此函数可以看到调用SetWindowsHookEx,此函数在消息处理链中塞入了函数_AfxCbtFilterHook,再来看看_AfxCbtFilterHook函数做了些什么!
先贴出源码:
消息分为命令消息、控件通行以及窗口消息,上面的函数中OnCommand处理命令消息,OnNotify处理控件通知,窗口消息的处理是查找MessageMap对应条目然后调用处理函数,查找MessageMap时向基类的MessageMap回溯,我们的例子程序中最先查找的MyFrame的MessageMap,之后依次调用CFrameWnd,CWnd,CCmdTarget,CObject的
MessageMap,从上面的说明中可以得到窗口消息的传导路径是直接从派生类传递到基类的。
下面来看看处理命令消息的OnCommand函数和处理控件通知消息的OnNotify函数。
调用OnCommand函数的窗口指针是MyFrame类型,所以实际要调用的是MyFrame::OnCommand,MyFrame中没有重写此函数,那么调用的就是CFrameWnd::OnCommand,进入后接着调用CWnd::OnCommand,在此函数中又调用了CFrameWnd::OnCmdMsg,此函数代码如下:
重上面的代码可以看出,命令消息先由CView处理,再交由CFrameWnd处理,最后给CWinApp处理,而且被其中一个类型处理之后就不会再被后面的类型处理。这里OnCmdMsg函数调用的都是CCmdTarget::OnCmdMsg函数,此函数源码如下:
此函数也是依次从派生类到基类查找消息处理表,调用处理函数,从上面的说明可以知道,一条命令消息的处理路径是先传递给CView的派生类,之后传递给CFrameWnd的派生类及基类,最后传递给CWinApp的派生类。
CWnd::OnNotify也是调用了CFrameWnd::OnCmdMsg,所以控件通知和命令消息的处理路径一样。
先贴出源码:
LRESULT CALLBACK
_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
if (code != HCBT_CREATEWND)
{
// wait for HCBT_CREATEWND just pass others on...
return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,
wParam, lParam);
}
ASSERT(lParam != NULL);
LPCREATESTRUCT lpcs = ((LPCBT_CREATEWND)lParam)->lpcs;
ASSERT(lpcs != NULL);
CWnd* pWndInit = pThreadState->m_pWndInit;
BOOL bContextIsDLL = afxContextIsDLL;
if (pWndInit != NULL || (!(lpcs->style & WS_CHILD) && !bContextIsDLL))
{
...
ASSERT(wParam != NULL); // should be non-NULL HWND
HWND hWnd = (HWND)wParam;
WNDPROC oldWndProc;
if (pWndInit != NULL)
{
AFX_MANAGE_STATE(pWndInit->m_pModuleState);
// the window should not be in the permanent map at this time
ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);
// connect the HWND to pWndInit...
pWndInit->Attach(hWnd);
// allow other subclassing to occur first
pWndInit->PreSubclassWindow();
WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();
ASSERT(pOldWndProc != NULL);
// subclass the window with standard AfxWndProc
WNDPROC afxWndProc = AfxGetAfxWndProc();
oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,
(DWORD_PTR)afxWndProc);
ASSERT(oldWndProc != NULL);
if (oldWndProc != afxWndProc)
*pOldWndProc = oldWndProc;
pThreadState->m_pWndInit = NULL;
}
else
{
ASSERT(!bContextIsDLL); // should never get here
...
}
}
lCallNextHook:
LRESULT lResult = CallNextHookEx(pThreadState->m_hHookOldCbtF, code,
wParam, lParam);
...
return lResult;
}
在上面一段代码中我们看到此构造函数只拦截了窗口创建消息的处理,其他消息并没有拦截,并在处理时调用
SetWindowLongPtr(hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc);
如果你学过Win32编程,一定知道这个调用就是来修改窗口的处理函数的,这个函数把主窗口的处理函数修改成了afxWndProc,这个是MFC内部定义函数,其有又调用了AfxCallWndProc,此函数调用CWnd::WindowProc,在CWnd::WindowProc中又调用CWnd::OnWndMsg,在CWnd::OnWndMsg中就可以看到OnCommand,OnNotify函数调用,以及在消息映射表中查找消息的过程,精简之后源码如下: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;
}
...
const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();
UINT iHash; iHash = (LOWORD((DWORD_PTR)pMessageMap) ^ message) & (iHashMax-1);
winMsgLock.Lock(CRIT_WINMSGCACHE);
AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash];
const AFX_MSGMAP_ENTRY* lpEntry;
if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)
{
...
}
else
{
// not in cache, look for it
pMsgCache->nMsg = message;
pMsgCache->pMessageMap = pMessageMap;
for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;
pMessageMap = (*pMessageMap->pfnGetBaseMap)())
{
// Note: catch not so common but fatal mistake!!
// BEGIN_MESSAGE_MAP(CMyWnd, CMyWnd)
ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
if (message < 0xC000)
{
// constant window message
if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,
message, 0, 0)) != NULL)
{
pMsgCache->lpEntry = lpEntry;
winMsgLock.Unlock();
goto LDispatch;
}
}
else
{
// registered windows message
lpEntry = pMessageMap->lpEntries;
while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL)
{
UINT* pnID = (UINT*)(lpEntry->nSig);
ASSERT(*pnID >= 0xC000 || *pnID == 0);
// must be successfully registered
if (*pnID == message)
{
pMsgCache->lpEntry = lpEntry;
winMsgLock.Unlock();
goto LDispatchRegistered;
}
lpEntry++; // keep looking past this one
}
}
}
pMsgCache->lpEntry = NULL;
winMsgLock.Unlock();
return FALSE;
}
LDispatchRegistered: // for registered windows messages
ASSERT(message >= 0xC000);
ASSERT(sizeof(mmf) == sizeof(mmf.pfn));
mmf.pfn = lpEntry->pfn;
lResult = (this->*mmf.pfn_l_w_l)(wParam, lParam);
LReturnTrue:
if (pResult != NULL)
*pResult = lResult;
return TRUE;
}
消息分为命令消息、控件通行以及窗口消息,上面的函数中OnCommand处理命令消息,OnNotify处理控件通知,窗口消息的处理是查找MessageMap对应条目然后调用处理函数,查找MessageMap时向基类的MessageMap回溯,我们的例子程序中最先查找的MyFrame的MessageMap,之后依次调用CFrameWnd,CWnd,CCmdTarget,CObject的
MessageMap,从上面的说明中可以得到窗口消息的传导路径是直接从派生类传递到基类的。
下面来看看处理命令消息的OnCommand函数和处理控件通知消息的OnNotify函数。
调用OnCommand函数的窗口指针是MyFrame类型,所以实际要调用的是MyFrame::OnCommand,MyFrame中没有重写此函数,那么调用的就是CFrameWnd::OnCommand,进入后接着调用CWnd::OnCommand,在此函数中又调用了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;
}
重上面的代码可以看出,命令消息先由CView处理,再交由CFrameWnd处理,最后给CWinApp处理,而且被其中一个类型处理之后就不会再被后面的类型处理。这里OnCmdMsg函数调用的都是CCmdTarget::OnCmdMsg函数,此函数源码如下:
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)
{
return _AfxDispatchCmdMsg(this, nID, nCode,
lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);
}
}
return FALSE; // not handled
}
此函数也是依次从派生类到基类查找消息处理表,调用处理函数,从上面的说明可以知道,一条命令消息的处理路径是先传递给CView的派生类,之后传递给CFrameWnd的派生类及基类,最后传递给CWinApp的派生类。
CWnd::OnNotify也是调用了CFrameWnd::OnCmdMsg,所以控件通知和命令消息的处理路径一样。
下面借用《深入浅出MFC》中的一张图来作个总结,MFC中的消息处理路径如图中箭头所示: