MFC消息映射的原理:笔记
多态的实现机制有两种,一是通过查找绝对位置表,二是查找名称表;两者各有优缺点,那么为什么mfc的消息映射采用了第二种方法,而不是c++使用的第一种呢?因为在mfc的gui类库是一个庞大的继承体系,而里面的每个类有很多成员函数(只说消息反映相关的成员函数啊),而且在派生类中,需要改写的也比较少(我用来做练习的程序就是那么一两个,呵呵)。那么用c++的虚函数的实现机制会导致什么问题呢?就是大量虚表的建立使得空间浪费掉很多。
嗯…怎么办呢?于是各大c++名库(比如QT,MFC,VCL…)在消息映射的实现方面,抛开了虚函数的方式,而用了第二种方法:查找名称表,其原理五花八门,各显神通,让我想到了春秋时代,各国诸侯置周天子不顾,挟天子令诸侯,各自为政的阶段,呵呵~
现在先说MFC的做法:MFC消息映射机制的原理,也就是MFC是怎么做到一个消息来了,就调用相应的成员函数的?(在VC编程里面用的是消息循环机制,那比较好理解,就是在消息处理程序里面来个大大的swicth…)
好了,在c++ stl成熟的现在,很多人都会想到用map来匹配(据我所知,有些公司很喜欢这样用,抛开了mfc的做法),但是当时stl没有流行,所以mfc设计者们就来个链表查找。
先看用法:
首先,要用消息处理的类,必须要继承自CcmdTarget类;
然后,在类的声明中有如下的宏:DECLARE_MESSAGE_MAP()和需要实现的消息映射
然后,在类的实现文件中,有如下宏:BEGIN_MESSAGE_MAP(…), … END_MESSAGE_MAP()
具体例子如下所示:
//头文件中:
class CMainFrame : public CFrameWnd
{
……
// 生成的消息映射函数
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
};
//实现文件中:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_CREATE()
END_MESSAGE_MAP()
那些宏展开之后如下所示:
class CMainFrame : public CFrameWnd
{
……
// 生成的消息映射函数
protected:
int OnCreate(LPCREATESTRUCT lpCreateStruct);
// 下三行为宏DECLARE_MESSAGE_MAP()的展开
protected:
static const AFX_MSGMAP* PASCAL GetThisMessageMap();
virtual const AFX_MSGMAP* GetMessageMap() const;
};
// 下10行为宏BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)的展开
const AFX_MSGMAP* CmainFrame::GetMessageMap() const
{
return GetThisMessageMap();
}
const AFX_MSGMAP* CMainFrame::GetThisMessageMap()
{
typedef CmainFrame ThisClass;
typedef CframeWnd TheBaseClass;
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
// 下2行为宏ON_WM_CREATE()的展开
{ WM_CREATE, 0, 0, 0, AfxSig_is, (AFX_PMSG) (AFX_PMSGW)(static_cast
< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > ( &ThisClass :: OnCreate)) },
// 下5行为宏END_MESSAGE_MAP()的展开
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
static const AFX_MSGMAP messageMap = { &TheBaseClass::GetThisMessageMap, _messageEntries[0] };
return &messageMap;
}
上面这些几乎全是数据结构的初始化代码,先不管这里面的结构放的是什么内容,先看类声明那个宏展开的两个函数究竟在mfc框架的那个地方使用?(先看过程,在看数据结构,这是面向对象分析的不二法门~)
下面,说一下别人总结的mfc流程中的消息分派,(参考文章会放在附录里面的,这只是笔记嘛,呵呵)
注意:我安装的是VS2005,MFC源码在安装的目录下面的 VC/atlmfc/src/mfc目录里面
1、 先假定mfc的消息入口点是CWnd::WindowProc函数(至于是如何流入的,请参考其他文章),其代码就好像vc编程里面那个WinMain函数的消息循环部分差不多:
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = 0;
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}
看,就是看一下该消息在OnWndWsg里是否找到对应的处理函数,如果没找到,用DefWindowProc处理;
2、 那么,这个OnWndWsg里面就是调用以上宏展开的那个函数来取得相关的消息映射滴~简化过后的代码如下:
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
LRESULT lResult = 0;
const AFX_MSGMAP* pMessageMap;
//取得消息映射结构,GetMessageMap为虚函数,所以实际取的是CmainFrame的消息映射
pMessageMap = GetMessageMap();
// 查找对应的消息处理函数
for (pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap)
if (message < 0xC000)
if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0)) != NULL)
goto LDispatch;
... ...
LDispatch:
//通过联合来匹配正确的函数指针类型
union MessageMapFunctions mmf;
mmf.pfn = lpEntry->pfn;
……
其中的pMessageMap = GetMessageMap();语句要留意,因为GetMessageMap是虚函数,在上面的CmainFrame类中已经重写了,所以,调用的实际上是CmainFrame的GetMessageMap;返回一个表格的指针pMessageMap,然后根据消息的类型在里面寻找,这个表格的数据结构如下:
struct AFX_MSGMAP
{
const AFX_MSGMAP* (* pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY* lpEntries;
};
再看CmainFrame中的代码:
static const AFX_MSGMAP messageMap = { &TheBaseClass::GetThisMessageMap, _messageEntries[0] };
就知道:这个表格其实是一个链表,放的是基类的GetMassageMap函数指针,和当前类的真正表格_messageEntries的入口地址。再看_messageEntries里面放的又是什么:
nMessage |
nCode |
nID |
nLastID |
nSig |
nPfn |
WM_CREATE |
0 |
0 |
0 |
AfxSig_is |
&CmainClass::OnCreate |
0 |
0 |
0 |
0 |
AfxSig_end |
0 |
好了,现在看到了WM_CREATE 就连着 &CmainClass::OnCreate,再结合上面CWnd::OnWndMsg的代码:
for (; pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap)
if (message < 0xC000)
if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0)) != NULL)
goto LDispatch;
就可以推理得到,AfxFindMessageEntry就是在找message和_messageEntries.nMessage的相等,如果不等,就往基类里面找: pMessageMap = pMessageMap->pBaseMap
找到了就跳到Ldispatch,其中,有:mmf.pfn = lpEntry->nPfn;这个就是放着该消息处理函数的内容。
3、 总结:
其实那些宏的作用就是用来初始化那个链表messageMap 和当前类处理函数的表格_messageEntries,当框架代码运行到pMessageMap = GetMessageMap();时候,由于虚函数的作用,使得当面运行当前窗口CmainFrame的GetMassageMap,把CmainFrame的表格给pMessageMap,然后再从派生类往基类一层层找响应的处理函数。
这与c++虚函数机制相比,其好处是可以省空间,每个类里面只有自己重写的函数信息,其他信息在基类里面找;
不好的地方就是搜链表,从当前类往基类上搜,时间上可能久一点;
mfc消息分派的基本原理就是这样的,但是还有很多的细节没有深入,比如那些不同的成员函数有不同的参数,他们是怎么传递的,怎么在表格里面统一参数的信息...这些看下面的参考文献【1】,有较为详细的说明。
4、 补充:用的框架代码里面,最显著的是一种模式:模板方法模式(template method pattern)。具体
的说明请参考其他文章。
这里为了补充,举一个例子说明是怎么调用的:
class A
{
public:
void callPrint(){ print(); };
protected:
static void printThis(){ cout << "this is a A object!/n"; };
private:
virtual void print(){ printThis(); };
};
class B : public A
{
protected:
static void printThis(){ cout << "This is a B object!/n";}
private:
void print(){ printThis();}
};
如下语句输出什么:
B b;
A *pa = &b;
pa->callPrint();
也许你知道输出的是“this is a B object!/n”,但是,知道为什么嘛?参考文章【3】中有详细的讲解,会令你满意的。
(注意上面例子中的virtual和访问权限,试着利用虚函数的实现机制来解释他是怎么作用的。)
参考文章:
【1】、MFC消息分派:http://blog.csdn.net/linzhengqun/archive/2007/11/28/1905671.aspx
【2】、MFC教程之消息映射的实现:http://www.vczx.com/tutorial/mfc/mfc4.php
【3】、与大虾对话:领悟设计模式:http://www.myfaq.com.cn/A200508/2005-08-07/183608.html
MFC消息映射的原理:笔记的更多相关文章
- MFC消息映射与命令传递
题外话:刚开始学视窗程序设计的时候,我就打印了一本Windows消息详解,里面列举了各种已定义消息的意义和作用,共10多页,在编程的时候翻翻,有时觉得很受用.我发觉很多编程的朋友,虽然每天都面对消息, ...
- MFC消息映射机制以及画线功能实现
---此仅供用于学习交流,切勿用于商业用途,转载请注明http://www.cnblogs.com/mxbs/p/6213404.html. 利用VS2010创建一个单文档标准MFC工程,工程名为Dr ...
- MFC编程入门之五(MFC消息映射机制概述)
在MFC软件开发中,界面操作或者线程之间通信都会经常用到消息,通过对消息的处理实现相应的操作.比较典型的过程是,用户操作窗口,然后有消息产生,送给窗口的消息处理函数处理,对用户的操作做出响应. 一.什 ...
- VS2010/MFC编程入门之五(MFC消息映射机制概述)
VS2010/MFC编程入门之五(MFC消息映射机制概述)-软件开发-鸡啄米 http://www.jizhuomi.com/software/147.html 上一讲鸡啄米为大家简单分析了MFC应用 ...
- MFC消息映射机制
1.MFC应用框架主要类之间的关系 MFC自动生成的框架中重要的类有:C-App.CMainFrame.C-Doc和C-View. 其他的类如CClassView.CFileView等都是在框架窗口( ...
- MFC技术内幕系列之(四)---MFC消息映射与消息传递内幕
//////////////////////////////////////////////////////////////////////////////////// ...
- MFC 消息映射表和虚函数实现消息映射到底谁的效率高
深入浅出MFC对于虚函数实现方式的缺点,它指出:虚函数耗费大量内存,系统最终将被这些额外负担拖垮. 但是现在对于容量巨大的白菜价格的内存来说,这种额外负担是否已经过时了呢~? 书中提到,虚函数表 ...
- 复习总结《一》MFC消息映射
长时间人容易遗忘,从新捡起!特做下记录 MFC消息映射 1.在MFC中消息映射主要牵扯到三个宏分别为: DECLARE_MESSAGE_MAP() BEGIN_MESSAGE_MAP(theClass ...
- 深入浅出MFC——消息映射与命令传递(六)
1. 消息分类: 2. 万流归宗——Command Target(CCmdTarget): 3. "消息映射"是MFC内建的一个信息分派机制.通过三个宏(DECLARE_MESSA ...
随机推荐
- linux c 得到时间
ctime: 将时间和日期以字符串格式表示头文件: time.h函数定义: char *ctime(const time_t *timep); 应用举例:#include <stdio.h> ...
- JavaScript的实现
了解了JavaScript是干什么的< 对一些词的理解 >,下面该知道它是怎么实现的. 一个完整的JavaScript是由三部分组成的,如下图 ECMAScript 可以为不同种类的宿主环 ...
- 组件接口(API)设计指南-文件夹
组件接口(API)设计指南-文件夹 组件接口(API)设计指南[1]-要考虑的问题 组件接口(API)设计指南[2]-类接口(class interface) 组件接口(API)设计指南[3]-托付( ...
- 在ibatis下匹配特殊手机号码(oracle数据库)
<isNotNull prepend="AND" property="endNumber"> <isNotEmpty property=&qu ...
- 一天一个类,一点也不累 之 Vector
一天一个类,一点也不累. 今天要说的是ArrayList的亲兄弟--Vector 亲兄弟?看看“族谱” Class Vector<E> java.lang.Object java.util ...
- java--局部类只能访问外包方法的final局部成员
class B523{ // private int k = 10; public void go(int x, final int y){ // int a = x+y; final int b = ...
- Linux下搭建Hadoop集群
本文地址: 1.前言 本文描述的是如何使用3台Hadoop节点搭建一个集群.本文中,使用的是三个Ubuntu虚拟机,并没有使用三台物理机.在使用物理机搭建Hadoop集群的时候,也可以参考本文.首先这 ...
- 利用d3.js绘制中国地图
d3.js是一个比較强的数据可视化js工具. 利用它画了一幅中国地图,例如以下图所看到的: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3ZhcDE=/ ...
- MSSQL - SqlDataReader
DataReader对象: ·DataReader对象是一个读取行的只读流的方式,绑定数据时比使用数据集方式性能要高,因为他是只读的,所以如果要对数据库中的数据进行修改就需要借助 将所做的修改保存到数 ...
- 【linux】具体芯片MACHINE_START处理
欢迎转载,转载时需保留作者信息,谢谢. 邮箱:tangzhongp@163.com 博客园地址:http://www.cnblogs.com/embedded-tzp Csdn博客地址:http:// ...