事实上,MFC 4.x 利用hook,把看似无关的动作全牵联起来了。所谓hook,是Windows
程序设计中的一种高阶技术。通常消息都是停留在消息队列中等待被所隶属之窗口抓
取,如果你设立hook,就可以更早一步抓取消息,并且可以抓取不属于你的消息,送往
你设定的一个所谓「滤网函数(filter)」。
请查阅Win32 API 手册中有关于SetWindowsHook 和SetWindowsHookEx 两函数,以获
得更多的hook 信息。(可参考Windows 95 A Developer s Guide 一书第6章Hooks)
MFC 4.x 的hook 动作是在每一个CWnd 衍生类别之对象产生之际发生,步骤如下:
// in WINCORE.CPP(MFC 4.x)
// 请回顾第6章「CFrameWnd::Create产生主窗口」一节
BOOL CWnd::CreateEx(...)
{
...
PreCreateWindow(cs); //
AfxHookWindowCreate(this);
HWND hWnd = ::CreateWindowEx(...);
...
}
// in WINCORE.CPP(MFC 4.x)
void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
...
pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,
_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
...
}
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 一
定会先被调用:
_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
{
_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
if (code != HCBT_CREATEWND)
{
// wait for HCBT_CREATEWND just pass others on...
return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,
wParam, lParam);
}
...
if (!afxData.bWin31)
{
// perform subclassing right away on Win32
_AfxStandardSubclass((HWND)wParam);
}
else
{
...
}
...
LRESULT lResult = CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,
wParam, lParam);
return lResult;
}
void AFXAPI _AfxStandardSubclass(HWND hWnd)
{
...
// subclass the window with standard AfxWndProc
oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC,
(DWORD)AfxGetAfxWndProc());
}
WNDPROC AFXAPI AfxGetAfxWndProc()
{
...
return &AfxWndProc;
}
啊,非常明显,上面的函数合力做了偷天换日的勾当:把「窗口所属之Windows 窗口类
别」中所记录的窗口函数,改换为AfxWndProc。于是,::DispatchMessage 就把消息源源
推往AfxWndProc 去了。
这种看起来很迂回又怪异的作法,是为了包容新的3D Controls(细节就容我省略了吧),
并与MFC 2.5 兼容。下图把前述的hook 和subclassing 动作以流程图显示出来:
不能稍息,我们还没有走出迷宫!AfxWndProc 只是消息两万五千里长征的第一站!
两万五千里长征- 消息的流动
一个消息从发生到被攫取,直至走向它的归宿,是一条漫漫长路。上一节我们来到了漫
漫长路的起头AfxWndProc,这一节我要带你看看消息实际上如何推动。
消息的流动路线已隐隐有脉络可寻, 此脉络是指由BEGIN_MESSAGE_MAP 和
END_MESSAGE_MAP 以及许许多多ON_WM_xxx 宏所构成的消息映射网。但是唧筒与
方向盘是如何设计的?一切的线索还是要靠源代码透露:
// in WINCORE.CPP(MFC 4.x)
LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
...
// messages route through message map
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
WPARAM wParam = 0, LPARAM lParam = 0)
{
...
// delegate to object's WindowProc
lResult = pWnd->WindowProc(nMsg, wParam, lParam);
...
return lResult;
}
整个MFC 中,拥有虚拟函数WindowProc 者包括CWnd、CControlBar、COleControl、
COlePropertyPage、CDialog、CReflectorWnd、CParkingWnd。一般窗口(例如Frame 视
窗、View 窗口)都衍生自CWnd,所以让我们看看CWnd::WindowProc。这个函数相当
于C++ 中的窗口函数:
// in WINCORE.CPP(MFC 4.x)
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// OnWndMsg does most of the work, except for DefWindowProc call
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)
{
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);
}