进入MFC讲坛的前言(四)
MFC的消息映射机制
MFC的设计者们在设计MFC时,紧紧把握一个目标,那就是尽可能使得MFC的代码要小,速度尽可能快。为了这个目标,他们使用了许多技巧,其中很多技巧体现在宏的运用上,实现MFC的消息映射的机制就是其中之一。
同MFC消息映射机制有关的宏有下面几个:
DECLARE_MESSAGE_MAP()宏
BEGIN_MESSAGE_MAP(theClass, baseClass)和END_MESSAGE_MAP()宏
弄懂MFC消息映射机制的最好办法是将找出一个具体的实例,将这些宏展开,并找出相关的数据结构。
DECLARE_MESSAGE_MAP()
DECLARE_MESSAGE_MAP()宏的定义如下:
#define DECLARE_MESSAGE_MAP() \
private: \
static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
static AFX_DATA const AFX_MSGMAP messageMap; \
virtual const AFX_MSGMAP* GetMessageMap() const; \
从上面的定义可以看出,DECLARE_MESSAGE_MAP()作下面三件事:
定义一个长度不定的静态数组变量_messageEntries[];
定义一个静态变量messageMap;
定义一个虚拟函数GetMessageMap();
在DECLARE_MESSAGE_MAP()宏中,涉及到MFC中两个对外不公开的数据结构
AFX_MSGMAP_ENTRY和AFX_MSGMAP。为了弄清楚消息映射,有必要考察一下这两个数据结构的定义。
AFX_MSGMAP_ENTRY的定义
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
结构中各项的含义注释已经说明得很清楚了,这里不再多述,从上面的定义你是否看出,AFX_MSGMAP_ENTRY结构实际上定义了消息和处理此消息的动作之间的映射关系。因此静态数组变量_messageEntries[]实际上定义了一张表,表中的每一项指定了相应的对象所要处理的消息和处理此消息的函数的对应关系,因而这张表也称为消息映射表。再看看AFX_MSGMAP的定义。
(2)AFX_MSGMAP的定义
struct AFX_MSGMAP
{
const AFX_MSGMAP* pBaseMap;
const AFX_MSGMAP_ENTRY* lpEntries;
};
不难看出,AFX_MSGMAP定义了一单向链表,链表中每一项的值是一指向消息映射表的指针(实际上就是_messageEntries的值)。通过这个链表,使得在某个类中调用基类的的消息处理函数很容易,因此,“父类的消息处理函数是子类的缺省消息处理函数”就“顺理成章”了。在后面的“MFC窗口的消息处理”一节中会对此作详细的讲解。
BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()
它们的定义如下:
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return &theClass::messageMap; } \
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
{ &baseClass::messageMap, &theClass::_messageEntries[0] }; \
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
{ \
#define END_MESSAGE_MAP() \
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
对应BEGIN_MESSAGE_MAP()的定义可能不是一下子就看得明白,不过不要紧,举一例子就很清楚了。对于BEGIN_MESSAGE_MAP(CView, CWnd),VC预编译器将其展开成下面的形式:
const AFX_MSGMAP* CView::GetMessageMap() const
{
return &CView::messageMap;
}
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CView::messageMap =
{
&CWnd::messageMap,
&CView::_messageEntries[0]
};
AFX_COMDAT const AFX_MSGMAP_ENTRY CView::_messageEntries[] =
{
至于END_MESSAGE_MAP()则不过定义了一个表示映射表结束的标志项,我想大家对于这种简单的技巧应该是很熟悉的,无需多述。
到此为止,我想大家也已经想到了象ON_COMMAND这样的宏的具体作用了,不错它们只不过定义了一种类型的消息映射项,看看ON_COMMAND的定义:
#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },
根据上面的定义,ON_COMMAND(ID_FILE_NEW, OnFileNew)将被VC预编译器展开
如下:
{WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv,
(AFX_PMSG)&OnFileNew},
到此,MFC的消息映射机制已经清楚了,现在提出并解答两个问题以作为对这一节的小结。
为什么不直接使用虚拟函数实现消息处理函数呢?这是一个GOOD QUESTION。前面已经说过,MFC的设计者们在设计MFC时有一个很明确的目标,就是使得“MFC的代码尽可能小,速度尽可能快”,如果采用虚拟函数,那么对于所有的窗口消息,都必须有一个与之对应的虚拟函数,因而对每一个从CWnd派生的类而言,都会有一张很大的虚拟函数表vtbl。但是在实际应用中,一般只对少数的消息进行处理,大部分都交给系统缺省处理,所以表中的大部分项都是无用项,这样做就浪费了很多内存资源,这同MFC设计者们的设计目标是相违背的。当然,MFC所使用的方法只是解决这类问题的方式之一,不排除还有其他的解决方式,但就我个人观点而言,这是一种最好的解决方式,体现了很高的技巧性,值得我们学习。
至于这第二个问题,是由上面的问题引申出来的。如果在子类和父类中出现了相同的消息出来函数,VC编译器会怎么处理这个问题呢?VC不会将它们看作错误,而会象对待虚拟函数类似的方式去处理,但对于消息处理函数(带afx_msg前缀),则不会生成虚拟函数表vtbl。
进入MFC讲坛的前言(四)的更多相关文章
- 进入MFC讲坛的前言(五)
框窗.视图和文档及其关系 MFC架构的另外一个特色是它的框窗.视图和文档这个三位一体的结构,它是一个典型的MVC(Model.View and Controler)结构.严格的讲,框窗不属于MVC中的 ...
- 进入MFC讲坛的前言(二)
MFC的WinMain 使用MFC编程的程序员刚开始都会提出这样一个问题:我的程序是从哪儿开始执行的?回答是:从WinMain()开始执行的.提出这样的问题是由于在他们所编写的MFC应用中看不到Win ...
- 进入MFC讲坛的前言(一)
在这里,我想谈谈自己学习MFC的一些体会.我是从1997年才开始在Window下编写程序的.在这之前,我编写过一些DOS程序,包括一个简单的全屏幕编辑器和一个带函数的表达式解释器,都是一些小的程序.W ...
- 进入MFC讲坛的前言(三)
MFC中的窗口创建及窗口消息映射 我经常碰到有人问我有关窗口创建的问题,他们经常把用HWND描述的系统窗口对象和用CWnd描述的MFC的窗口对象混淆不清.这两者之间是紧密联系在一起的,但是MFC为了自 ...
- 深入浅出MFC——MFC骨干程序(四)
1. 熟记MFC类层次结构: 2. AppWizard可以为我们制作出MFC程序骨干: 3. Document/View支撑你的应用程序:Document/View的价值在于,这些MFC类已经把一个应 ...
- MFC 小知识总结四
1 PlaySound 播放WAV格式的音乐 This function plays a sound specified by a file name, resource, or system ev ...
- mfc中Button、Edit Control和MFC EditBrowse Control的用法
[前(fei)言(hua)] 写LL(1)分析器被CString转string卡了一个多小时也是醉了. 趁着还算清醒写下这次用到的控件的使用方法好了. 这次实验的mfc用到了四个控件:Edit Con ...
- MFC编程入门
一. 什么是MFC? 如果你要建立一个Windows应用程序,应该如何下手? 好的开端是从设计用户界面开始. 首先,你要决定什么样的用户能使用该程序并根据需要来设置相应的用户界面对象.Windows用 ...
- VC++ 6.0 C8051F340 MFC programming note
/************************************************************************************** * VC++ 6.0 C ...
随机推荐
- 静态编译Qt5.4.1和Qt WebKit(网事如风的blog)good
blog文章地址:http://godebug.org/index.php/archives/133/ WebKit是个好东西,做爬虫.显示网页还是想用HTML来做桌面应用的界面都可以用他,不过一直以 ...
- Ubuntu系统下在Eclipse中使用真实手机运行调试Android应用
本文是在Ubuntu下已搭建好JRE+Eclipse+AndroidSDK的环境中进行的. 如果从未进行过如下的操作的话,在运行Android应用时,在设备选择器那里你的手机会显示为一大串" ...
- Sicily-1156
一. 模仿树的的先序遍历.范围是1000个节点.用数组存储节点的信息. 二. 要注意的是,头结点是不确定的,所以在前序遍历之前要找出头结点,除了头结点的下标值出现一次之外,其他结点 ...
- [置顶] 【J2SE 】1136 容器之旅
开篇引言 本篇文章我将要详细的介绍一下什么是容器?以及什么是1136?来系统全面的了解容器,以及容器的应用,下面就进入我们的容器之旅吧! 1.什么是容器? 用来存储和组织其他对象的对象.我们也可以这样 ...
- 高可用mysql集群搭建
对web系统来说,瓶颈大多在数据库和磁盘IO上面,而不是服务器的计算能力.对于系统伸缩性我们一般有2种解决方案,scale-up(纵向扩展)和scale-out(横向扩展).前者如扩内存,增加单机性能 ...
- 设计模式的C++实现 2.工厂模式
工厂模式,实例化对象,用工厂方法取代new操作. 工厂模式基本与简单工厂模式差点儿相同,简单工厂中每次加入一个子类必须在工厂类中加入一个推断分支,这违背了开闭原则.而工厂模式的解决方法是将简单工厂中的 ...
- java IO之字节流和字符流-Reader和Writer以及实现文件复制拷贝
接上一篇的字节流,以下主要介绍字符流.字符流和字节流的差别以及文件复制拷贝.在程序中一个字符等于两个字节.而一个汉字占俩个字节(一般有限面试会问:一个char是否能存下一个汉字,答案当然是能了,一个c ...
- android程序报错“error launching activity com.android.ddmlib.shellcommandunresponsiveexception”的解决方式
今天在调试android程序的时候,因为是NDK开发,要先编译.so库再打包下载到android模拟器,所以花费的时间比較长.控制台就会报例如以下错误: error launching activit ...
- http://localhost:8080/ 演出Oracle说明
输入http://localhost:8080,可以出现tomcat主页.并且我检查过啦,jdk.tomcat及oracle中的环境变量都设置对啦,可是安装oracle之后,再输入http://loc ...
- c 围圈报数
#include<stdio.h> /*围圈报数*/ void left(int *p,int m,int n) { int i,j,count; i = j = count = ; ) ...