1.前面已经在MFC六大关键技术之仿真中描述了大概的原理,现在我们来继续深入这个问题。
2.MFC把消息分为三类:
①命令消息(WM_COMMAND):命令消息意味着 “使用者命令程序做某些操作”。凡由UI对象产生的消息都是这种命令消息,可能来自菜单或加速键或工具栏按钮,并且都以WM_COMMAND呈现,如何分辨来自各处的命令消息?SDK程序主要靠消息的wParam识别之,MFC程序则主要靠菜单项的识别码(menu ID)识别之—-两者其实是相同的。
什么样的类有资格接受命令消息?凡派生自CCmdTarget的类,皆有资格,从command target的字面意义可知,这是命令消息的目的地。也就是说,凡派生自CCmdTarget者,它的骨子里就有了一种特殊的机制。把整张MFC类层次图摊开来看,几乎建立应用程序的最重要的几个类都派生自CCmdTarget,剩下的不能接受消息的,是像CFile、CArchive、CPoint、CDao(数据库)、Collection Classes(纯粹数据处理)、GDI等等“非主流”类。
②标注消息:除WM_COMMAND之外,任何以WM_开头的都算是这一类。任何派生自CWnd之类,均可接收此消息。
③Control Notification:这种消息由控件产生,为的是向其父窗口(通常是对话框)通知某种情况。例如当你在ListBox上选择其中一个项目,ListBox就会产生LBN_SELCHANGE传送给父窗口。这类消息也是以WM_COMMAND形式呈现。
3.三个奇怪的宏
第一个宏:BEGIN_MESSAGE_MAP有两个参数,分别是拥有此消息映射表的类,及其父类。
第二个宏:ON_COMMAND,指定命令消息的处理函数名称。还可以有许多种,ON_WM_CHAR、ON_WM_CLOSE之类。
第三个宏:END_MESSAGE_MAP作为结尾记号。
4.我们说过,所有能够接收消息的类,都应该派生自CCmdTarget。那么我们这么推论应该是合情合理:每一个派生自CCmdTarget的类都应该有上面的三个宏?
不对!CWinThread就没有。
那么CWinApp通往CCmdTarget的路径不就断掉了吗?不,不,不…
我们来看CWinApp的宏:
BEGIN_MESSAGE_MAP(CWinApp, CCmdTarget) //注意第二个参数是CCmdTarget,而不是CWinThread。
5.为什么MFC采用消息映射机制而不采用虚函数呢?
要知道,虚函数必须经由一个虚函数表实现出来,每一个子类必须有它自己的虚函数表,其内至少有父类之虚函数表的内容复本。那好,虚函数表中的每一个项目都是一个函数指针,价值4字节,如果基类的虚函数表有100项,经过10层继承,开枝散叶,总共需耗费多少内存在其中?最终,系统会被巨大的额外负担拖垮!
6.MFC2.5的CWinApp::Run调用PumpMessage,后者又调用::DispatchMessage,把消息源源推往AfxWndProc,最后流向pWnd->WindowProc去。
MFC4.x仍使用AfxWndProc作为消息唧筒的起点,但其间却隐藏了许多关节。
事实上,MFC4.x利用hook,把看似无关的操作全牵连起来了。所谓hook,是Windows程序设计中的一种高级技术。通常消息都是停留在消息队列中等待被所隶属的窗口抓取,如果你设立hook,就可以更早一步抓取消息,并且可以抓取不属于你的消息,送往你设定的一个所谓的“过滤函数filter”。
MFC4.x的hook操作是在每一个CWnd派生类之对象时发生,在任何窗口即将产生之前,过滤函数一定会先被调用。我们直接来看图:
7.整个MFC中,拥有虚函数WindowProc者包括CWnd、CControlBar、CDialog…一般窗口(Frame窗口,View窗口)都派生自CWnd,所以直接看Cwnd::WindowProc,相当于窗口函数。
Cwnd::WindowProc调用了OnWndMsg用来分辨并处理消息,如果是命令消息,就交给OnCommand处理,如果是通知消息(Notification),就交给OnNotify处理。为啥要区别?一般的Windows消息,直接在消息映射表中上溯,寻找其消息处理程序,而命令消息的上溯路径不是那么单纯地只往父类去,他们可能要拐弯。
8.直线上溯:
比较消息映射表,如果吻合就调用表中项目所记录的函数。
9.拐弯上溯:
下面是这个命令消息的流浪过程:
1. MDI 主窗口( CMDIFrameWnd) 收到命令消息WM_COMMAND, 其ID 为
ID_EDIT_CLEAR_ALL。
2. MDI 主窗口把命令消息交给目前作用中的MDI 子窗口(CMDIChildWnd)。
3. MDI 子窗口给它自己的子窗口(也就是View)一个机会。
4. View 检查自己的Message Map。
5. View 发现没有任何处理例程可以处理此命令消息,只好把它传给Document。
6. Document 检查自己的Message Map,它发现了一个吻合项:
BEGIN_MESSAGE_MAP(CScribbleDoc, CDocument)
ON_COMMAND(ID_EDIT_CLEAR_ALL, OnEditClearAll)
…
END_MESSAGE_MAP()
于是调用该函数,命令消息的流动路线也告终止。
如果上述的步骤6 仍没有找到处理函数,那么就:
7. Document 把这个命令消息再送到Document Template 对象去。
8. 还是没被处理,于是命令消息回到View。
9. View 没有处理,于是又回给MDI 子窗口本身。
10. 传给CWinApp 对象– 无主消息的终极归属。
10.AfxSig_xx的奥秘
当我们找到消息的处理程序后,怎么知道要交给消息处理程序哪些参数呢?
由于消息处理函数的类型各异,MFC使用了AfxSig_来说明消息处理函数的类型。在找到某消息的消息处理函数之后,判断其类型再进行响应转换。
union MessageMapFunctions mmf;
mmf.pfn=lpEntry->pfn;
switch(lpEntry->nSig)
{
case AfxSig_isg:
lResult=(this->*mmf.pfn_is)(LPTSTR)lParam);
break;
case Afx_Sig_lwl:
lResult=(this->*mmf.pfn_lwl)(wParam,lParam);
break;
case AfxSig_vv:
(this->*mmf.pfn_vv)();
break;
……..
}
注意:MessageMapFunctions 和AfxSig_ 他们定义于AFXMSG_.H文件。位于:C:\Program Files (x86)\Microsoft Visual Studio\VC98\MFC\Include
真正的函数只有一个pfn,但通过union,它有许多不同的形象。
AfxSig_is代表参数为LPTSTR字符串,返回值为int.
Afx_lwl代表参数wiewParam和lParam。返回值为LRESULT。
Afx_vv代表参数和返回值都为void.
(this->*mmf.pfn_vv)();中的pfn_vv是union MessageMapFunctions的一个成员。如
union MessageMapFunctions
{
AFX_PMSG pfn;
bool (AFX_MSG_CALL CWnd::*pfn_bD)(CDC*);
void (AFX_MSG_CALL CWnd::*pfn_VV)(CDC*);
………….
};
注意MessageMapFunctions是union类型的哦。
11.ON_COMMAND和ON_UPDATE_COMMAND_UI的运行。
转载请标注来源:http://www.alonemonkey.com
By:AloneMonkey