消息的一生神秘而漫长,期间曲折多为人所不知。今天就让我们一起“撩”一下这位高冷的神秘妹纸吧。
事实上,MFC中利用hook技术,把看似无关的操作关联了起来。所谓hook(钩子)是Windows中一种高级的编程技术,它可以保证,在特定情况发生的时候就转去执行我们所指定的操作(是一种霸道机关术)。
MFC的hook发生在CWnd派生类对象的产生之际。在WINCORE.CPP中我们可以看到如下的代码:
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);
……
}
在WINCORE.CPP中又有:
voidAFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
……
if (pThreadState->m_hHookOldCbtFilter== NULL)
{
pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,
_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());//关键操作
if (pThreadState->m_hHookOldCbtFilter== NULL)
AfxThrowMemoryException();
}
……
}
WH_CBT是hook类型中的一种,它意味着安装一个Computer-Based- Training(CBT)滤网函数。安装后,Windows系统在进行一下任何一个操作之前都会先调用我们的滤网函数:
◎激活一个窗口(HCBT_ACTIVATE)
◎产生或摧毁一个窗口(HCBT_CREATEWND、HCBT_DESTROYWND)
◎最大化或最小化一个窗口(HCBT_MINMAX)
◎移动或缩放一个窗口(HCBT_MOVESIZE)
◎完成一个来自系统菜单的系统命令(HCBT_SYSTEMCOMMAND)
◎从系统队列中移去一个鼠标或键盘消息(HCBT_KEYSKIPPED、HCBT_CLICKSKIPPED)
因此,在上述hook安装之后,任何窗口产生之前,滤网函数_AfxCbtFilterHook都会先被调用(WINCORE.CPP):
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);
}
……
if (!afxData.bWin4 && !bContextIsDLL &&
(pCtl3dState = _afxCtl3dState.GetDataNA()) != NULL &&
pCtl3dState->m_pfnSubclassDlgEx != NULL &&
(dwFlags = AfxCallWndProc(pWndInit, hWnd, WM_QUERY3DCONTROLS)) != 0)
{
// was the class registered with AfxWndProc?
WNDPROC afxWndProc = AfxGetAfxWndProc();
BOOL bAfxWndProc = ((WNDPROC)
GetWindowLong(hWnd, GWL_WNDPROC) == afxWndProc);
pCtl3dState->m_pfnSubclassDlgEx(hWnd, dwFlags);
// subclass the window if not already wired to AfxWndProc
if (!bAfxWndProc) //若窗口处理函数不是AfxWndProc则设置之
{
// subclass the window with standard AfxWndProc
oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC,(DWORD)afxWndProc);
ASSERT(oldWndProc != NULL);
*pOldWndProc = oldWndProc;
}
}
……
}
我们可以看到AfxGetAfxWndProc函数的操作如下(WINCORE.CPP):
WNDPROC AFXAPI AfxGetAfxWndProc()
{
#ifdef _AFXDLL//为DLL时
return AfxGetModuleState()->m_pfnAfxWndProc;
#else
return &AfxWndProc;
#endif
}
由上可见,窗口类中所登记的窗口函数被一众函数强行设置为AfxWndProc。于是,当执行::DispatchMessage函数时,消息就被默默地送往AfxWndProc中去处理了。据说:这样迂回的做法是为了包容新的3D Controls,并与MFC2.5兼容。
好了,热身运动做完了,下面就来看看消息到底是怎么被处理的吧。我们已经知道,消息被抓取后将被送往AfxWndProc,该函数主要操作如下(WINCORE.CPP):
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
// special message which identifies the window as using AfxWndProc
if (nMsg == WM_QUERYAFXWNDPROC)
return 1;
// all other messages route through message map
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
ASSERT(pWnd != NULL);
ASSERT(pWnd->m_hWnd == hWnd);
if (pWnd == NULL || pWnd->m_hWnd != hWnd)
return ::DefWindowProc(hWnd, nMsg, wParam, lParam);
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
其中AfxCallWndProc的主要操作如下(WINCORE.CPP):
LRESULTAFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
WPARAM wParam = 0, LPARAM lParam = 0)
{
……
// delegate to object's WindowProc
lResult = pWnd->WindowProc(nMsg, wParam,lParam);
……
returnlResult;
}
其中的WindowProc函数如下(WINCORE.CPP):
LRESULTCWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// OnWndMsg does most of the work, exceptfor DefWindowProc call
LRESULT lResult = 0;
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}
其中调用的DefWindowProc主要操作如下(WINCORE.CPP):
LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)
{
if (m_pfnSuper != NULL)
return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);
WNDPROC pfnWndProc;
if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)
return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam);
else
return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);
}
WindowProc中还调用了OnWndMsg函数,该函数是分辨并处理消息的专职机构。如果是命令消息,就交给OnCommand处理,如果是通知消息,就交给OnNotify处理。WM_ACTIVATE和WM_SETCURSOR也都有特定的处理函数。而一般的Windows消息,就直接在消息映射表中上溯,寻找其处理程序。之所以要区分出命令消息WM_COMMAND和通知消息WM_NOTIFY,是因为它们的处理方式会更加地微妙(这个以后再说了)。
OnWndMsg的主要操作逻辑如下(WINCORE.CPP):
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
LRESULT lResult = 0;
// 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)pMessageMap) ^ message) & (iHashMax-1);
AfxLockGlobals(CRIT_WINMSGCACHE);
AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash];
const AFX_MSGMAP_ENTRY* lpEntry;
if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)//检查该消息是否存在cache之中
{
// cache hit
lpEntry = pMsgCache->lpEntry;
AfxUnlockGlobals(CRIT_WINMSGCACHE);
if (lpEntry == NULL)
return FALSE;
// cache hit, and it needs to be handled
if (message < 0xC000)
goto LDispatch;
else
goto LDispatchRegistered;
}
else
{
// not in cache, look for it
pMsgCache->nMsg = message;
pMsgCache->pMessageMap = pMessageMap;
for (/* pMessageMap already init'ed */; pMessageMap != NULL;
pMessageMap = pMessageMap->pBaseMap)
{
// Note: catch not so common but fatal mistake!!
// BEGIN_MESSAGE_MAP(CMyWnd, CMyWnd)
/*
利用AfxFindMessageEntry寻找消息映射表中对应的消息处理程序。如果找到,再按照message为一般消息(< 0xC000)或自行注册的消息(> 0xC000)分别跳转到LDispatch:或LDispatchRegistered:去执行
*/
if (message < 0xC000)
{
// constant window message
if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,message, 0, 0)) != NULL)
{
pMsgCache->lpEntry = lpEntry;
AfxUnlockGlobals(CRIT_WINMSGCACHE);
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;
AfxUnlockGlobals(CRIT_WINMSGCACHE);
goto LDispatchRegistered;
}
lpEntry++; // keep looking past this one
}
}
}
pMsgCache->lpEntry = NULL;
AfxUnlockGlobals(CRIT_WINMSGCACHE);
return FALSE;
}
ASSERT(FALSE); // not reached
LDispatch:
ASSERT(message < 0xC000);
union MessageMapFunctions mmf;
mmf.pfn = lpEntry->pfn;
int nSig;
nSig = lpEntry->nSig;
……
switch (nSig)
{
default:
ASSERT(FALSE);
break;
case AfxSig_bD:
lResult = (this->*mmf.pfn_bD)(CDC::FromHandle((HDC)wParam));
break;
case AfxSig_bb: // AfxSig_bb, AfxSig_bw, AfxSig_bh
lResult = (this->*mmf.pfn_bb)((BOOL)wParam);
break;
case AfxSig_bWww: // really AfxSig_bWiw
lResult = (this->*mmf.pfn_bWww)(CWnd::FromHandle((HWND)wParam),
(short)LOWORD(lParam), HIWORD(lParam));
break;
……
}
goto LReturnTrue;
LDispatchRegistered: // for registered windows messages
ASSERT(message >= 0xC000);
mmf.pfn = lpEntry->pfn;
lResult = (this->*mmf.pfn_lwl)(wParam, lParam);
LReturnTrue:
if (pResult != NULL)
*pResult = lResult;
return TRUE;
}
其中调用的AfxFindMessageEntry函数主要操作如下(WINCORE.CPP):
const AFX_MSGMAP_ENTRY* AFXAPI
AfxFindMessageEntry(const AFX_MSGMAP_ENTRY* lpEntry,
UINT nMsg, UINT nCode, UINT nID)
{
#if defined(_M_IX86) && !defined(_AFX_PORTABLE)
// 32-bit Intel 386/486 version.
//此处使用内联汇编做相应的处理,从而加快速度
……
#else // _AFX_PORTABLE
// C version of search routine
while (lpEntry->nSig != AfxSig_end)
{
if (lpEntry->nMessage == nMsg && lpEntry->nCode == nCode &&
nID >= lpEntry->nID && nID <= lpEntry->nLastID)
{
return lpEntry;
}
lpEntry++;//AFX_MSGMAP_ENTRY指针的++操作
}
return NULL; // not found
#endif // _AFX_PORTABLE
}
由上面的函数逻辑可知,对于一般的消息,所进行的操作只是比较消息映射表,如果有吻合的项目,就调用表中所记录的函数。比较得对象主要有两个,优先在MFC中的cache中查找,没有命中的话,查找的第二个对象就是建立的消息映射表。比较成功后转到执行函数时,有一个庞大的switch/case结构,这里主要是为了保证类型安全。
假如,有WM_PAINT消息发生于CMyView窗口中,则消息的流动路线如下图所示: