《MFC篇》消息映射机制

消息映射机制

什么是消息映射机制?

MFC使用消息映射机制来处理消息,引入了消息映射表的概念,表中存消息消息处理函数二者对应关系。当鼠标点击事件发生时,会产生对应消息,然后去消息映射表中查找对应的消息处理函数并执行。

什么是句柄?

句柄相当于一个编号,Windows对于我们来说相当于一个黑盒,我们只能通过这个编号,也就是句柄来获得我们想要的数据。(个人理解,句柄存了一个地址,地址所在位置存储了一些必须的信息,如,调用打印机句柄,获得打印机信息,也可以调用打印功能)

消息机制为什么要使用宏?

设计者在设计MFC时,尽可能的使MFC的代码要小,速度尽可能快。为了这个目的,设计师使用了很多奇技淫巧,使用宏就是其中之一。

消息映射机制的宏

MFC消息机制的宏有下面几个:

// 1、
DECLARE_MESSAGE_MAP()
// 2、
BEGIN_MESSAGE_MAP()(theClass,baseClass)
// 3、
END_MESSAGE_MAP()

DELCARE_MESSAGE_MAP

DECLARE_MESSAGE_MAP()宏展开:

#define DECLARE_MESSAGE_MAP()
private:
	static const AX_MSGMAP_ENTRY _messageEntries[];
protected:
	static AFX_DATA const AFX_MSGMAP messageMap;
	virtual const AFX_MSGMAP* GetMessageMap() const;

从上面定义可以看出,DECLARE_MESSAGE_MAP()做了三件事,
(1)定义了一个长度不定的静态数组变量_messageEntries[];
(2)定义了一个静态变量messageMap;
(3)定义了一个虚拟函数GetMessageMap();

AFX_MSGMAP_ENTRY和AFX_MSGMAPX

DECLARE_MESSAGE_MAP()宏中的两个对外不公开的结构struct,即AFX_MSGMAP_ENTRY和AFX_MSGMAPX。
AFX_MSGMAP_ENTRY的定义:

struct AFX_MSGMAP_ENTRY
{
	UNIT nMessage;// windows message
	UNIT nCode;// control code or WM_NOTIFY code
	UNIT nID;// control ID
	UNIT nLastID;// used for entries specifying a range of control id’s
	UNIT nSig;// signature type (action) or pointer to messagge
	AFX_PMSG pfn;// routine to call (or special value)
};

从注释可以看出,AFX_MSGMAP_ENTRY结构实际上定义了消息和处理此消息的函数之间的映射关系。因此静态数组_messageEntries[],实际上定义了一张表,表中每一项指定了消息和消息处理函数的对应关系,也就是消息映射表。nMessage和nCode确定一条消息的内容,nID和nLastID确定了一条消息的来源,nSig和pfn确定了消息处理函数和调用方式。
此外,名字ENTRY也有入口的意思(即消息和消息处理函数之间的映射)。
AFX_MSGMAP的定义:

struct AFX_MSGMAP
	{
		const AFX_MSGMAP* pBaseMap;
		consgt AFX_MSGMAP_ENTRY* lpEntries;
}

AFX_MSGMAP定义了两个指针,pBaseMap指向另一个AFX_MSGMAP,lpEntries指针指向消息映射表_messageEntries。通过这种方式,使得在某个类中调用基类的消息处理函数很容易,因此,“父类的消息处理函数是子类的缺省消息处理函数”就“顺理成章”了。

BEGIN_MESSAGE_MAP

作用:定义DECLARE_MESSAGE_MAP宏声明的静态变量。

#define BEEGIN_MESSAGE_MAP(theClass,baseClass)
   const AFX_MSGMAP* theClass::GetMessageMap() const 
   {
	return &theClass::messageMap;
   }
   AFX_COMDAT 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有两个参数,theClass表示当前类,baseClass表示父类。
BEGIN_MESSAGE_MAO首先定义了函数GetMessageMap的函数体,返回成员变量messageMap的地址。
然后,AFX_COMDAT const AFX_MSGMAP theClass::messageMap=??,初始化了成员变量messageMap。messageMap的pBaseMap指向父类的messageMap,lpEntries指针指向当前类的_messageEntries数组的首地址。
最后,AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[]=??,定义了_messageEntries数组初始化的开始部分代码, 即一个“{”。

END_MESSAGE_MAP

作用:定义_messageEntries数组初始化的结束部分代码,即“{0,0,0,0,AfxSig_end,(AFX_PMSG)0} };”

#define END_MESSAGE_MAP()
   {0,0,0,0,AfxSig_end,(AFX_PMSG)0}
	 };

其他宏

在DECLARE_MESSAGE_MAP和END_MESSAGE_MAP之间还有一些宏,如ON_COMMAND、ON_WM_CREATE等,这些宏最终都会被生成一条AFX_MSGMAP_ENTRY结构的数据,并成为消息映射表_messageEntries的一个元素。
以ON_COMMAND宏为例:

#define ON_COMMAND(id,memberFxn)
	{
		WM_COMMAND, CN_COMMAND, (WORD)id,(WORD)id, AfxSigCmd_v,
		static_cast<AFX_PMSG>(memberFxn)
	}

参考
https://blog.csdn.net/weixin_42083441/article/details/109726786
https://www.jianshu.com/p/c1cdba489b88
https://www.cnblogs.com/zzw19940404/p/14008341.html
https://blog.csdn.net/lovemysea/article/details/74893961
https://blog.csdn.net/zhangchuan7758/article/details/121653589

消息映射机制入门

消息映射中的消息是什么?

窗口消息一般由三部分组成:
1、一个无符号整数,是消息值
2、消息附带的WPARAM类型的参数
3、消息附带LPARAM类型的参数。其实我们一般说的消息值,也就是一个无符号整数,经常被定义为宏。

消息映射机制的形象比喻

原文链接:https://zhuanlan.zhihu.com/p/489889991

前几天赵工忽然凑到我脸上来,问我晓不晓得MFC的消息机制原理。

当时我正在努力摸鱼,被这猝不及防闯入的笑脸吓的差点打出一套闪电五连鞭。

我说你别逗我了,你干这一行的时间比我吃饭都多,问谁也不应该来问我这菜鸟吧。

赵工笑的脸更大了,一个劲的叫我说说嘛。

我忽然明白过来,他也想摸鱼,那我就不困了。

我们都知道MFC是一种window程序设计,一种基于事件驱动的程序设计模式。

这种事件驱动,主要是消息。

什么叫消息呢?

比如你是键盘侠,那得敲键盘,按下键盘就是消息;

比如你浏览网页,那得用鼠标点击,按下鼠标就是消息;

比如你想看岛国的学习资料,那得有个硬盘吧,插入硬盘就是消息。

在MFC窗口中,我们看到的现象是:我们点了个按钮,触发了消息机制,MFC程序执行了一些我们事先写好的操作。

不过,我们点下按钮或者敲下键盘,首先捕获到这个消息的,并不是程序——而是操作系统。

这就好比你扫微信付款给店家,你以为开开心心付了钱,店家开开心心收到钱了,实际上首先收到这笔钱的,是微信付款的后台系统。

image

操作系统获取到消息后,就会自动将其存入消息队列中,无论你按了多少个消息,就算是网上重拳出击的键盘侠,发出的每一个键盘消息都会被存入消息队列中。

消息队列这东西只有一个特定,那就是先入先出,先存进去的消息会先被派出去。

想一下,假如十个人给店家付款,先付款的反而店家没有先收到钱,店家慌了,你也慌了,估计怎么也得上演一集霍元甲大战黄飞鸿的好戏。

image

消息队列一收到存进来消息,高兴坏了,来活了。

它们就负责将消息按照顺序一个一个派送给应用程序,丝毫不敢怠慢,打工人没得选择。

经过了消息——操作系统——消息队列——应用程序,MFC应用程序才在真正接收到了消息。

image

然后应用程序会把消息再分发给操作系统,让操作系统去执行响应函数。

但是应用程序也做了些东西的,它负责将消息进行翻译,虽然再分发。

image

为什么要翻译?

很简单啊,就比如说组合键,一套CTRL+C,CTRL+V,你不翻译一下,系统真以为你是想发CTRL,C,CRTL,V这几个信号。

翻译完成后才会分发给操作系统,由操作系统来执行窗口过程。

image

消息驱动和事件驱动的异同

参考链接:https://blog.csdn.net/li_xunhuan/article/details/105574498
参考链接:https://www.zhihu.com/question/30393750

消息驱动和事件驱动的共同点,都是先有一个事件,事件产生相应的消息,再把消息放入消息队列,由需要的项目获取。他们的区别是消息是谁产生的。
以鼠标点击为例:
1、消息驱动:鼠标只管自己点击,不需要和系统过多的交互,消息由系统(或者第三方程序)循环检测,来捕获消息放入消息队列,消息对于鼠标点击事件来说是被动产生,高内聚。
2、事件驱动:鼠标点击产生点击事件后,要向系统发送消息“我点击了”的消息,消息是主动产生的。再发送到消息队列中。事件往往会将事件源包装起来。
备注:事件源:发生事件的对象。(即当前操作的对象)

消息驱动和事件驱动优缺点:
1、消息驱动耦合低,跨模块好用。
2、事件驱动耦合高,同模块内好用。
3、事件驱动是侵入式的,霸占主循环;消息驱动非侵入式的,将主循环该怎样设计的自由留给用户。
4、如果你在设计一个东西举棋不定,那么可以看看win32的GetMessage,本身就是一个耦合度极低的接口,又足够自由,接口任何语言都很方便,也可以在其基础上再封装成事件驱动。

posted @ 2023-04-13 09:25  Fusio  阅读(803)  评论(0编辑  收藏  举报