MFC消息响应机制及映射机制理解

一.MFC消息响应机制分析

---- MFCWindows下程序设计的最流行的一个类库,但是该类库比较庞杂,尤其是它的消息映射机制,更是涉及到很多低层的东西,我们在这里,对它的整个消息映射机制进行了系统的分析,可以帮助程序开发人员对MFC的消息映射机制有一个比较透彻的了解。

---- 关键词:面向对象 消息映射 MFC 程序设计

1.引言
---- VC++
MFC类库实际上是WindowsC++编程的一套最为流行的类库。MFC的框架结构大大方便了程序员的编程工作,但是为了更加有效、灵活的使用MFC编程,了解MFC的体系结构往往可以使编程工作事半功倍。它合理的封装了WIN32 API函数,并设计了一套方便的消息映射机制。但这套机制本身比较庞大和复杂,对它的分析和了解无疑有助于我们写出更为合理的高效的程序。这里我们简单的分析MFC的消息响应机制,以了解MFC是如何对Windows的消息加以封装,方便用户的开发。
2. SDK
下的消息机制实现
----
这里简单的回顾一下SDK下我们是如何进行Windows的程序开发的。一般来说,Windows的消息都是和线程相对应的。即Windows会把消息发送给和该消息相对应的线程。在SDK的模式下,程序是通过GetMessage函数从和某个线程相对应的消息队列里面把消息取出来并放到一个特殊的结构里面,一个消息的结构是一个如下的STRUCTURE
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;
----
其中hwnd表示和窗口过程相关的窗口的句柄,message表示消息的ID号,wParamlParam表示和消息相关的参数,time表示消息发送的时间,pt表示消息发送时的鼠标的位置。

---- 然后TranslateMessage函数用来把虚键消息翻译成字符消息并放到响应的消息队列里面,最后DispatchMessage函数把消息分发到相关的窗口过程。然后窗口过程根据消息的类型对不同的消息进行相关的处理。在SDK编程过程中,用户需要在窗口过程中分析消息的类型和跟消息一起的参数的含义,做不同的处理,相对比较麻烦,而MFC把消息调用的过程给封装起来,使用户能够通过ClassWizard方便的使用和处理Windows的各种消息。

3MFC的消息实现机制
----
我们可以看到,在MFC的框架结构下,可以进行消息处理的类的头文件里面都会含有DECLARE_MESSAGE_MAP(),这里主要进行消息映射和消息处理函数的声明。可以进行消息处理的类的实现文件里一般都含有如下的结构。
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)
//{{AFX_MSG_MAP(CInheritClass)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()


----
这里主要进行消息映射的实现和消息处理函数的实现。
----
所有能够进行消息处理的类都是基于CCmdTarget类的,也就是说CCmdTarget类是所有可以进行消息处理类的父类。CCmdTarget类是MFC处理命令消息的基础和核心。

---- 同时MFC定义了下面的两个主要结构:

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
struct AFX_MSGMAP
{
#ifdef _AFXDLL
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
const AFX_MSGMAP* pBaseMap;
#endif
const AFX_MSGMAP_ENTRY* lpEntries;
};

其中AFX_MSGMAP_ENTRY结构包含了
一个消息的所有相关信息,其中

nMessageWindows消息的ID
nCode
为控制消息的通知码
nID
Windows控制消息的ID
nLastID
表示如果是一个指定范围的消息被映射的话,
nLastID
用来表示它的范围。
nSig
表示消息的动作标识
AFX_PMSG pfn
它实际上是一个指向
和该消息相应的执行函数的指针。

---- AFX_MSGMAP主要作用是两个,一:用来得到基类的消息映射入口地址。二:得到本身的消息映射入口地址。

---- 实际上,MFC把所有的消息一条条填入到AFX_MSGMAP_ENTRY结构中去,形成一个数组,该数组存放了所有的消息和与它们相关的参数。同时通过AFX_MSGMAP能得到该数组的首地址,同时得到基类的消息映射入口地址,这是为了当本身对该消息不响应的时候,就调用其基类的消息响应。

---- 现在我们来分析MFC是如何让窗口过程来处理消息的,实际上所有MFC的窗口类都通过钩子函数_AfxCbtFilterHook截获消息,并且在钩子函数_AfxCbtFilterHook中把窗口过程设定为AfxWndProc。原来的窗口过程保存在成员变量m_pfnSuper中。

---- 所以在MFC框架下,一般一个消息的处理过程是这样的。

函数AfxWndProc接收Windows操作系统发送的消息。

函数AfxWndProc调用函数AfxCallWndProc进行消息处理,这里一个进步是把对句柄的操作转换成对CWnd对象的操作。

函数AfxCallWndProc调用CWnd类的方法WindowProc进行消息处理。注意AfxWndProcAfxCallWndProc都是AFXAPI函数。而WindowProc已经是CWnd的一个方法。所以可以注意到在WindowProc中已经没有关于句柄或者是CWnd的参数了。

方法WindowProc调用方法OnWndMsg进行正式的消息处理,即把消息派送到相关的方法中去处理。消息是如何派送的呢?实际上在CWnd类中都保存了一个AFX_MSGMAP的结构,而在AFX_MSGMAP结构中保存有所有我们用ClassWizard生成的消息的数组的入口,我们把传给OnWndMsgmessage和数组中的所有的message进行比较,找到匹配的那一个消息。实际上系统是通过函数AfxFindMessageEntry来实现的。找到了那个message,实际上我们就得到一个AFX_MSGMAP_ENTRY结构,而我们在上面已经提到AFX_MSGMAP_ENTRY保存了和该消息相关的所有信息,其中主要的是消息的动作标识和跟消息相关的执行函数。然后我们就可以根据消息的动作标识调用相关的执行函数,而这个执行函数实际上就是通过ClassWizard在类实现中定义的一个方法。这样就把消息的处理转化到类中的一个方法的实现上。举一个简单的例子,比如在View中对WM_LButtonDown消息的处理就转化成对如下一个方法的操作。
void CInheritView::OnLButtonDown
(UINT nFlags, CPoint point)
{
// TODO: Add your message
handler code here and/or call default
CView::OnLButtonDown(nFlags, point);
}

注意这里CView::OnLButtonDown(nFlags, point)实际上就是调用CWndDefault()方法。 Default()方法所做的工作就是调用DefWindowProc对消息进行处理。这实际上是调用原来的窗口过程进行缺省的消息处理。

如果OnWndMsg方法没有对消息进行处理的话,就调用DefWindowProc对消息进行处理。这是实际上是调用原来的窗口过程进行缺省的消息处理。
----
所以如果正常的消息处理的话,MFC窗口类是完全脱离了原来的窗口过程,用自己的一套体系结构实现消息的映射和处理。即先调用MFC窗口类挂上去的窗口过程,再调用原先的窗口过程。并且用户面对和消息相关的参数不再是死板的wParamlParam,而是和消息类型具体相关的参数。比如和消息WM_LbuttonDown相对应的方法OnLButtonDown的两个参数是nFlagspointnFlags表示在按下鼠标左键的时候是否有其他虚键按下,point更简单,就是表示鼠标的位置。
----
同时MFC窗口类消息传递中还提供了两个函数,分别为WalkPreTranslateTreePreTranslateMessage。我们知道利用MFC框架生成的程序,都是从CWinApp开始执行的,而CWinapp实际继承了CWinThread类。在CWinThread的运行过程中会调用窗口类中的WalkPreTranslateTree方法。而WalkPreTranslateTree方法实际上就是从当前窗口开始查找愿意进行消息翻译的类,直到找到窗口没有父类为止。在WalkPreTranslateTree方法中调用了PreTranslateMessage方法。实际上PreTranslateMessage最大的好处是我们在消息处理前可以在这个方法里面先做一些事情。举一个简单的例子,比如我们希望在一个CEdit对象里,把所有的输入的字母都以大写的形式出现。我们只需要在PreTranslateMessage方法中判断message是否为WM_CHAR,如果是的话,把wParam(表示键值)由小写字母的值该为大写字母的值就实现了这个功能。

---- 继续上面的例子,根据我们对MFC消息机制的分析,我们很容易得到除了上面的方法,我们至少还可以在另外两个地方进行操作。

---- 一:在消息的处理方法里面即OnChar中,当然最后我们不再调用CEdit::OnChar(nChar, nRepCnt, nFlags),而是直接调用DefWindowProc(WM_CHAR,nChar,MAKELPARAM (nRepCnt,nFlags))。因为从我们上面的分析可以知道CEdit::OnChar(nChar, nRepCnt, nFlags)实际上也就是对DefWindowProc方法的调用。

---- 二:我们可以直接重载DefWindowProc方法,message类型等于WM_CHAR的,直接修改nChar的值即可。

4.小结
----
通过对MFC类库的分析和了解,不仅能够使我们更好的使用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的定义。

  (2AFX_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

转载于:https://www.cnblogs.com/eric_lgf/archive/2009/08/19/1549835.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/549410.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

KMP算法 Next数组详解(【洛谷3375】KMP字符串匹配 )

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/qq_30974369/article/details/74276186 </div><div id"content_views" class"markdown_views"><!-- flowchart 箭头图标 勿删 --…

“顾问”

关于顾问的定义&#xff0c;有这么一个故事&#xff1a;一个客户花了大价钱启动了一个项目&#xff0c;这个项目主要解决的问题是&#xff1a;“现在几点了&#xff1f;”这时顾问走过来说&#xff1a;“请借你的手表用一下。”然后看了一下说&#xff1a;“现在是下午3点。”然…

转自知乎-我见过最通俗易懂的KMP算法详解

有些算法&#xff0c;适合从它产生的动机&#xff0c;如何设计与解决问题这样正向地去介绍。但KMP算法真的不适合这样去学。最好的办法是先搞清楚它所用的数据结构是什么&#xff0c;再搞清楚怎么用&#xff0c;最后为什么的问题就会有恍然大悟的感觉。我试着从这个思路再介绍一…

Sybase常见问题

1. Sybase数据库日志满碰到这种情况&#xff0c;常会出现能查询数据&#xff0c;但无法更新数据&#xff0c;启动事务等。在代码中跟踪可以发现&#xff0c;连接能够打开&#xff0c;但事务却无法开启。常会提示“Connection to Sybase server has been lost. All active trans…

Redis在Window服务下的安装

Redis 安装1.首先在Windows下下载安装Redis下载地址&#xff1a;https://github.com/MicrosoftArchive/redis/releases根据你电脑系统的实际情况选择32位还是64位&#xff0c;在这里我下载了的是Redis-x64-3.0.500.zip压缩包&#xff0c;压缩后得到解压文件.2.测试运行打开一个…

在一个禁止离婚的国家结婚

爱尔兰是全世界结婚率最低的国家&#xff0c;作为一个信奉天主教的国度&#xff0c;这个国家是禁止离婚的。很多适逢婚龄的男男女女大都选择观望&#xff0c;迟迟不去婚姻登记部门办理手续。所以&#xff0c;在爱尔兰流行不婚和晚婚。但一对中国夫妻在爱尔兰的结婚经历&#xf…

【计算机网络】时延、发送时延、传输时延、处理时延、排队时延、时延带宽积

版权声明&#xff1a;答应我&#xff0c;右上角点个「赞」再走好么&#xff5e;不甚感激&#xff5e;&#xff08;卖萌脸ヾ(^▽^)ノ https://blog.csdn.net/liuchuo/article/details/52893629 </div><link rel"stylesheet" href"https://csdnim…

ASP.NET (C#) 面试笔试题目收集

1. 简述 private、 protected、 public、 internal 修饰符的访问权限。 答 . private : 私有成员, 在类的内部才可以访问。 protected : 保护成员&#xff0c;该类内部和继承类中可以访问。 public : 公共成员&#xff0c;完全公开&#xff0c;没有访问限制。 internal: 在同一…

排队时延(Queuing delay)

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请注明出处。 https://blog.csdn.net/zhangskd/article/details/18224897 </div><link rel"stylesheet" href"https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-f57…

传播时延、发送时延、处理时延和排队时延各自的物理意义是什么?

传播时延、发送时延、处理时延和排队时延各自的物理意义是什么&#xff1f; 传播时延: 信号在传输通道上产生的时延 发送时延&#xff1a;以一定的速率发送完一个一定长度报文所需的时间 处理时延&#xff1a;节点进行报文存储转发处理所产生的时间 排队时延&#xff1a;报文…

爬山法实现 八皇后问题 (Python 实现)

本文主要简单阐述爬山法的基本算法思想&#xff0c;并给出用此算法实现八皇后问题详细过程 最基本的爬上搜索算法表示&#xff1a;(节选自《人工智能》第二版)&#xff1a; function HILL-CLIMBING(problem) return a state thate is a locak maximum inputs: problem …

系统容灾备份选型的决策表

冷备Master/SlaveMaster/Master两阶段提交Paxos一致性差最终一致性最终一致性强一致性强一致性事务不支持全局事务本地事务全局事务全局事务延迟低低低高高吞吐高高高低中等数据丢失概率很多很少很少不会丢失不会丢失Failover无只读读/写读/写读/写摘自对Google App Engine Dat…

至誠而不動者

至誠而不動者&#xff0c;未之有也&#xff1b; 吾學問廿年齡亦而立然未能解斯一語&#xff0c;今茲關左之行願以身驗之&#xff0c;若乃死生大事姑置焉。 巳未五月 二十一會猛士

八皇后问题和八数码问题的最陡上升爬山法、首选爬山法、随机重启爬山法、模拟退火算法的分析和实现

对经典算法的问题的回顾与感想 对八皇后问题和八数码问题分别用最陡上升爬山法、首选爬山法、随机重启爬山法、模拟退火算法来实现&#xff0c;并且分析他们的性能。 分析 要求实现的各个算法是有共同点的&#xff0c;比如&#xff0c;八皇后问题相关算法拥有相同的状态空间&…

Domino的压缩数据库的Load Compact命令

以下各表描述了完成 Compact 服务器任务时可以使用的选项。第一列列出了在 Domino Administrator 中使用“任务”“开始”工具或“文件”附签运行 Compact 时显示的选项。第二列列出了等价的命令行选项&#xff0c;可以在使用控制台命令或使用“程序”文档运行 Compact 时使用。…

go数据结构整理

初始化基本一样&#xff0c;不同点需要注意&#xff0c;make使用&#xff0c;nil变量的访问 array 初始化&#xff1a; 57 //修改元素值函数&#xff0c;指针版本和副本版本&#xff0c;指针版会修改原值58 func modify(arr [3]int, i int, v int) {59 //func modify(arr …

配置BGP

配置BGP<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />一、 实验目的:配置BGP协议,实现两个isp之间的通信二、 实验拓扑结构图三、实验步骤1. 基本接口配置(略) 2. 配置bgp协议isp<?xml:namespace prefix …

临时表和内存表的区别

看到很多朋友对这些概念有些混淆&#xff0c;我来发表下我自己的观点。内存表&#xff1a;1. 参数控制&#xff1a;max_heap_table_size2. 到达上线后报错。3. 表定义保存在磁盘上&#xff0c;数据和索引保存在内存里面。4. 不能包含TEXT,BLOB等字段。临时表&#xff1a;1. 参数…

哈工大C语言公开课练兵编程(二)

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/juejing2271/article/details/79854773 </div><div id"content_views" class"markdown_views"><!-- flowchart 箭头图标 勿删 --…

MyXls初级教程

这些天使用MyXls导出Excel报表&#xff08;因为Apose.Cells要收费&#xff09;。感觉MyXls虽然功能远没有Cells强大&#xff0c;但是胜在开源、免费而且性能稳定可靠。用作出一般情况下的报表。足矣&#xff01; 记下几个初级使用方法&#xff0c;希望能够给初入门的人一点帮助…