MFC的消息实现机制

我们可以看到,在MFC的框架结构下,可以进行消息处理的类的头文件里面都会含有DECLARE_MESSAGE_MAP()宏,这里主要进行消息映射和消息处理函数的声明。可以进行消息处理的类的实现文件里一般都含有如下的结构。

步骤/方法

  • 01

    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结构包含了 一个消息的所有相关信息,其中 nMessage为Windows消息的ID号 nCode为控制消息的通知码 nID为Windows控制消息的ID nLastID表示如果是一个指定范围的消息被映射的话, nLastID用来表示它的范围。 nSig表示消息的动作标识 AFX_PMSG pfn 它实际上是一个指向 和该消息相应的执行函数的指针。

  • 02

    而AFX_MSGMAP主要作用是两个,一:用来得到基类的消息映射入口地址。二:得到本身的消息映射入口地址。 实际上,MFC把所有的消息一条条填入到AFX_MSGMAP_ENTRY结构中去,形成一个数组,该数组存放了所有的消息和与它们相关的参数。同时通过 AFX_MSGMAP能得到该数组的首地址,同时得到基类的消息映射入口地址,这是为了当本身对该消息不响应的时候,就调用其基类的消息响应。 现在我们来分析MFC是如何让窗口过程来处理消息的,实际上所有MFC的窗口类都通过钩子函数_AfxCbtFilterHook截获消息,并且在钩子函 数_AfxCbtFilterHook中把窗口过程设定为AfxWndProc。原来的窗口过程保存在成员变量m_pfnSuper中。 所以在MFC框架下,一般一个消息的处理过程是这样的。 函数AfxWndProc接收Windows操作系统发送的消息。 函数AfxWndProc调用函数AfxCallWndProc进行消息处理,这里一个进步是把对句柄的操作转换成对CWnd对象的操作。 函 数AfxCallWndProc调用CWnd类的方法WindowProc进行消息处理。注意AfxWndProc和AfxCallWndProc都是 AFX的API函数。而WindowProc已经是CWnd的一个方法。所以可以注意到在WindowProc中已经没有关于句柄或者是CWnd的参数 了。

  • 03

    方法WindowProc调用方法OnWndMsg进行正式的消息处理,即把消息派送到相关的方法中去处理。消息是如何派送的呢? 实际上在CWnd类中都保存了一个AFX_MSGMAP的结构,而在AFX_MSGMAP结构中保存有所有我们用ClassWizard生成的消息的数组 的入口,我们把传给OnWndMsg的message和数组中的所有的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)实际上就是调用CWnd的Default()方法。 而Default()方法所做的工作就是调用DefWindowProc对消息进行处理。这实际上是调用原来的窗口过程进行缺省的消息处理。 如果OnWndMsg方法没有对消息进行处理的话,就调用DefWindowProc对消息进行处理。这是实际上是调用原来的窗口过程进行缺省的消息处理。 所以如果正常的消息处理的话,MFC窗口类是完全脱离了原来的窗口过程,用自己的一套体系结构实现消息的映射和处理。即先调用MFC窗口类挂上去的窗口过 程,再调用原先的窗口过程。并且用户面对和消息相关的参数不再是死板的wParam和lParam,而是和消息类型具体相关的参数。比如和消息 WM_LbuttonDown相对应的方法OnLButtonDown的两个参数是nFlags和point。nFlags表示在按下鼠标左键的时候是否 有其他虚键按下,point更简单,就是表示鼠标的位置。

  • 04

    同时MFC窗口类消息传递中还提供了两个函数,分别为WalkPreTranslateTree和PreTranslateMessage。我们知道利用 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的值即可。

    小结

    • 01

      通过对MFC类库的分析和了解,不仅能够使我们更好的使用MFC类库,同时,对于我们自己设计和实现框架和类,无疑也有相当大的帮助。

    (0)

    相关推荐

    • MFC消息映射机制

      摘要:关于MFC消息映射机制的深入研究. 步骤/方法 01 预备工作: 定义消息描绘结构: struct AFX_MSGMAP { AFX_MSGMAP* pBaseMessageMap; AFX_M ...

    • 如果MFC的消息映射表需要排序

      这是今天下班前和同事讨论的问题. MFC的消息映射通过几个简单的宏,对Windows的消息机制做了非常好的面向对象封装,一时为无数C++程序员所模仿(当然,MFC可能也是模仿别人的).熟悉MFC消息映 ...

    • Windows、Linux、Android、IOS 开发编程经验

      回想起来,从写一行"Hello World"到如今,已经有七八年了,回想起来,真正让自己技术得到提升的关键点有三个方面,一个是"实战项目",一个是"技 ...

    • QQ for Mac V1.4.0清新来袭 舞动你的Mac生活

      Mac时尚简约,清新典雅的生活态度,引得无数果粉追捧,而QQ for Mac简洁的界面、实用高效的即时沟通功能以及流畅的操作体验更是紧紧抓住了文艺青年、时尚小资的心。现在,全新的QQ for Mac ...

    • Windows 8的23大新功能盘点

      据悉,相较于Windows 7操作系统,Windows 8的用户界面不会有太大的变化,但是系统内核会有很大的改进。 当前,网络中有关Windows 8的传闻有很多,但是当前很难确定Windows 8中 ...

    • 安卓和iOS到底有啥区别?

      这个话题就像月经问题,总是不断.果粉喷安卓粉,安卓粉喷果粉,总是会抓住对方的缺点,然后痛数一番,这种事情我们可以在各个群里看见,聊着聊着吵起来了.我们呢,作为设计爱好者,要理性对待这些问题,谁让他们是 ...

    • MFC多线程编的可能

      之所以是"可能",因为这里有个重点就是临时对象是HWND操作的封装,不是窗口类的封装.因此所有的HWND临时对象都是CWnd的实例,即使上面强行转换为CAbcDialog*也依旧是 ...

    • MFC对多线程编程的支持

      摘要:关于MFC对多线程编程的支持深入研究. 步骤/方法 01 例程3 MultiThread3 传送一个结构体给一个线程函数也是可能的,可以通过传送一个指向结构体的指针参数来完成.先定义一个结构体: ...

    • Linux 内存机制详解宝典

      一. 我们先来查看一个内存使用的例子: [oracle@db1 ~]$ free -m total used free shared buffers cached Mem: 72433 67075 5 ...