友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
深入浅出MFC第2版(PDF格式)-第77部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
559
…………………………………………………………Page 622……………………………………………………………
第篇 深入 MFC 程式設計
要知道,虚拟函数必须经由一个虚拟函数表(virtual function table ,vtable )实作出来,每
一个子类别必须有它自己的虚拟函数表,其内至少有父类别之虚拟函数表的内容复本(请
参考第2章「类别与对象大解剖」一节)。好哇,虚拟函数表中的每一个项目都是一个
函数指针,价值4 字节,如果基础类别的虚拟函数表有100 个项目,经过10 层继承,
开枝散叶,总共需耗费多少内存在其中?最终,系统会被巨大的额外负担(overhead )
拖垮!
这就是为什么MFC 采用独特的消息映射机制而不采用虚拟函数的原因。
米诺托斯 (Minotauros)与西修斯 (Theseus)
截至目前我还有一些细节没有交待清楚,像是消息的比对动作、消息处理例程的调用动
作、以及参数的传递等等,但至少现在可以先继续进行下去,我的目标瞄准消息唧筒(叫
邦浦也可以啦)。
窗口接收消息后,是谁把消息唧进消息映射网中?是谁决定消息该直直往父映射表走
去?还是拐向另一条路(请回头看看图9…2 )?消息的绕行路线,以及MFC 的消息唧
筒的设计,活像是米诺托斯的迷宫。不过别担心,我将扮演西修斯,让你免遭毒手。
米诺托斯(Minotauros ),希腊神话里牛头人身的怪兽,为克里特岛国王迈诺斯之妻所生。
迈诺斯造迷宫将米诺托斯藏于其中,每有人误入迷宫即遭吞噬。怪兽后为雅典王子西修
斯(Theseus )所杀。
MFC 2。5 (注意,是2。5 而非4。x)曾经在WinMain 的第一个重要动作AfxWinInit
之中, 自动为程序注册四个Windows 窗口类别, 并且把窗口函数一致设为
AfxWndProc :
560
…………………………………………………………Page 623……………………………………………………………
第9章 訊息映射與命令繞行
//in APPINIT。CPP ( )
MFC 2。5
BOOL AFXAPI AfxWinInit (HINSTANCE hInstance; HINSTANCE hPrevInstance;
LPSTR lpCmdLine; int nCmdShow)
{
。。。
// register basic WndClasses (以下开始注册窗口类别)
WNDCLASS wndcls;
wndcls。lpfnWndProc = AfxWndProc;
// Child windows no brush; no icon; safest default class styles
。。。
wndcls。lpszClassName = _afxWnd;
if (! ::RegisterClass(&wndcls))
return FALSE;
// Control bar windows
。。。
wndcls。lpszClassName = _afxWndControlBar;
if (! ::RegisterClass(&wndcls))
return FALSE;
// MDI Frame window (also used for splitter window)
。。。
if (!RegisterWithIcon (&wndcls; _afxWndMDIFrame; AFX_IDI_STD_MDIFRAME))
return FALSE;
// SDI Frame or MDI Child windows or views normal colors
。。。
if (!RegisterWithIcon (&wndcls; _afxWndFrameOrView; AFX_IDI_STD_FRAME))
return FALSE;
。。。
}
下面是AfxWndProc 的内容:
// in WINCORE。CPP ( )
MFC 2。5
LRESULT CALLBACK AFX_EXPORT
AfxWndProc (HWND hWnd; UINT message; WPARAM wParam; LPARAM lParam)
{
CWnd* pWnd;
pWnd = CWnd::FromHandlePermanent(hWnd);
ASSERT(pWnd != NULL);
ASSERT(pWnd…》m_hWnd == hWnd);
561
…………………………………………………………Page 624……………………………………………………………
第篇 深入 MFC 程式設計
LRESULT lResult = _AfxCallWndProc (pWnd; hWnd; message; wParam; lParam);
return lResult;
}
// Official way to send message to a CWnd
LRESULT PASCAL _AfxCallWndProc (CWnd* pWnd; HWND hWnd; UINT message;
WPARAM wParam; LPARAM lParam)
{
LRESULT lResult;
。。。
TRY
{
。。。
lResult = pWnd…》WindowProc (message; wParam; lParam);
}
。。。
return lResult;
}
MFC 2。5 的CWinApp::Run 调用PumpMessage ,后者又调用::DispatchMessage ,把消息源
源推往AfxWndProc (如上),最后流向pWnd…》WindowProc 去。拿SDK 程序的本质来
做比对,这样的逻辑十分容易明白。
MFC 4。x 仍旧使用AfxWndProc 作为消息唧筒的起点,但其间却隐藏了许多关节。
但愿你记忆犹新,第6章曾经说过,MFC 4。x 适时地为我们注册Windows 窗口类别(在
第一次产生该种型式之窗口之前)。这些个Windows 窗口类别的窗口函数各是「窗口所
对应之C++ 类别中的DefWindowProc 成员函数」,请参考第 6章 「CFrameWnd::Create
产生主窗口」一节。这就和MFC 2。5 的作法(所有窗口类别共享同一个窗口函数)有了
明显的差异。那么,推动消息的心脏,也就是CWinThread::PumpMessage 中调用
的::DispatchMessage (请参考第 6章 「CWinApp::Run 程序生命的活水源头」一节),
照说应该把消息唧到对应之C++ 类别的DefWindowProc 成员函数去。但是,我们发现
MFC 4。x 中仍然保有和MFC 2。5 相同的AfxWndProc ,仍然保有AfxCallWndProc ,而且
它们扮演的角色也没有变。
562
…………………………………………………………Page 625……………………………………………………………
第9章 訊息映射與命令繞行
事实上,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); // 第6章曾经详细讨论过此一函数。
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 类型中的一种,意味着安装一个puter…Based Training (CBT )
滤网函数。安装之后,Windows 系统在进行以下任何一个动作之前,会先调用你的滤网
函数:
令一个窗口成为作用中的窗口(HCBT_ACTIVATE )
产生或摧毁一个窗口(HCBT_CREATEWND 、HCBT_DESTROYWND )
最大化或最小化一个窗口(HCBT_MINMAX )
563
…………………………………………………………Page 626……………………………………………………………
第篇 深入 MFC 程式設計
搬移或缩放一个窗口(HCBT_MOVESIZE )
完成一个来自系统菜单的系统命令(HCBT_SYSTEMMAND )
从系统队列中移去一个鼠标或键盘消息 (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()
{
564
…………………………………………………………Page 627……………………………………………………………
第9章 訊息映射與命令繞行
。。。
return &AfxWndProc;
}
啊,非常明显,上面的函数合力做了偷天换日的勾当:把「窗口所属之Windows 窗口类
别」中所记录的窗口函数,改换为AfxWndProc 。于是,::DispatchMessage 就把消息源源
推往AfxWndProc 去了。
这种看起来很迂回又怪异的作法,是为了包容新的3D Controls (细节就容我省略了吧),
并与MFC 2。5 兼容。下图把前述的hook 和subclassing 动作以流程图显示出来:
CFrameWnd::LoadFrame
CFrameWnd::LoadFrame
CFrameWnd::Create
CFrameWnd::Create
CWnd::CreateEx
CWnd::CreateEx 安装WH_CBT hook ,设定滤网函数
为_AfxCbtFilterHook
AfxHookWindowCreate(this)
AfxHookWindowCreate(this)
::SetWindowsHookEx(WH_CBT; _AfxCbtFilterHook ;。。。)
::SetWindowsHookEx(WH_CBT; _AfxCbtFilterHook ;。。。)
::CreateWindowEx
::CreateWindowEx
_AfxCbtFilterHook 窗口产生之前一刻,由于曾安装过
_AfxCbtFilterHook WH_CBT hook,所以滤网函数
_AfxCbtFilterHook 会先被唤起。
_AfxStandardSubclass
_AfxStandardSubclass
SetWindowLong(hWnd; GWL_WNDPROC;&AfxWndProc)
SetWindowLong(hWnd; GWL_WNDPROC;&AfxWndProc)
WND structure
subclassing 动作;改 各种消息,来自
变窗口函数。 extra bytes ::DispatchMessage
message mapping and 的唧送,或由操作
mand routing 系统直接送达。
(through message map) CWL_WNDPROC
。
&AfxWndProc
message handler
message handler
565
…………………………………………………………Page 628……………………………………………………………
第篇 深入 MFC 程式設計
不能稍息,我们还没有走出迷宫!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)
566
…………………………………………………………Page 629……………………………………………………………
第9章 訊息映射與命令繞行
{
// 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 ((
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!