全文预览已结束
下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
MFC消息响应机制分析- MFC是Windows下程序设计的最流行的一个类库,但是该类库比较庞杂,尤其是它的消息映射机制,更是涉及到很多低层的东西,我们在这里,对它的整个消息映射机制进行了系统的分析,可以帮助程序开发人员对MFC的消息映射机制有一个比较透彻的了解。1引言- VC+的MFC类库实际上是Windows下C+编程的一套最为流行的类库。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号,wParam和lParam表示和消息相关的参数,time表示消息发送的时间,pt表示消息发送时的鼠标的位置。 - 然后TranslateMessage函数用来把虚键消息翻译成字符消息并放到响应的消息队列里面,最后DispatchMessage函数把消息分发到相关的窗口过程。然后窗口过程根据消息的类型对不同的消息进行相关的处理。在SDK编程过程中,用户需要在窗口过程中分析消息的类型和跟消息一起的参数的含义,做不同的处理,相对比较麻烦,而MFC把消息调用的过程给封装起来,使用户能够通过ClassWizard方便的使用和处理Windows的各种消息。 3MFC的消息实现机制- 我们可以看到,在MFC的框架结构下,可以进行消息处理的类的头文件里面都会含有DECLARE_MESSAGE_MAP()宏,这里主要进行消息映射和消息处理函数的声明。可以进行消息处理的类的实现文件里一般都含有如下的结构。 BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)/AFX_MSG_MAP(CInheritClass)/AFX_MSG_MAPEND_MESSAGE_MAP()- 这里主要进行消息映射的实现和消息处理函数的实现。 - 所有能够进行消息处理的类都是基于CCmdTarget类的,也就是说CCmdTarget类是所有可以进行消息处理类的父类。CCmdTarget类是MFC处理命令消息的基础和核心。 - 同时MFC定义了下面的两个主要结构: AFX_MSGMAP_ENTRYstruct AFX_MSGMAP_ENTRYUINT nMessage; / windows messageUINT nCode; / control code or WM_NOTIFY codeUINT nID; / control ID (or 0 for windows messages)UINT nLastID; / used for entries specifying a range of control idsUINT nSig; / signature type (action) or pointer to message #AFX_PMSG pfn; / routine to call (or special value);和AFX_MSGMAPstruct AFX_MSGMAP#ifdef _AFXDLLconst AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();#elseconst AFX_MSGMAP* pBaseMap;#endifconst AFX_MSGMAP_ENTRY* lpEntries;其中AFX_MSGMAP_ENTRY结构包含了一个消息的所有相关信息,其中nMessage为Windows消息的ID号nCode为控制消息的通知码nID为Windows控制消息的IDnLastID表示如果是一个指定范围的消息被映射的话,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进行消息处理。注意AfxWndProc和AfxCallWndProc都是AFX的API函数。而WindowProc已经是CWnd的一个方法。所以可以注意到在WindowProc中已经没有关于句柄或者是CWnd的参数了。 方法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更简单,就是表示鼠标的位置。 - 同时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的值即可。 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_ENTRYUINT nMessage; / windows messageUINT nCode; / control code or WM_NOTIFY codeUINT nID; / control ID (or 0 for windows messages)UINT nLastID; / used for entries specifying a range of control idsUINT 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_MSGMAPconst 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:_messageEntries0 ; 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:_messageEntries0 ;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下一个消息的处理过程是一般是这样的。1、_AfxCbtFilterHook截获消息(这是一个钩子函数)2、_AfxCbtFilterHook把窗口过程设定为AfxWndProc。3、函数AfxWndProc接收Windows操作系统发送的消息。4、函数AfxWndProc调用函数AfxCallWndProc进行消息处理。5、函数AfxCallWndProc调用CWnd类的方法WindowProc进行消息处理。如何添加自己的消息?我们已经了解了WINDOW的消息机制,如何加入我们自己的消息呢?好我们来看一个标准的消息处理程序是这个样子的在 CWnd 类中预定义了标准 Windows 消息 (WM_XXXXWM是WINDOW MESSAGE的缩写) 的默认处理程序。类库基于消息名命名这些处理程序。例如,WM_PAINT 消息的处理程序在 CWnd 中被声明为:afx_msg v
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 工程监理专业职业规划
- 病理技术员就业前景
- 未来中期职业规划作文
- 廉洁教育演讲模板-1
- 2026江西江西新鸿人力资源服务有限公司招聘4人笔试模拟试题及答案解析
- 2026浙江绍兴大学招聘27人考试备考题库及答案解析
- 2026广东湛江市霞山区东新街道办事处就业见习岗位招聘1人农业笔试备考试题及答案解析
- 2026浙江台州市椒江区三甲街道招聘4人笔试备考题库及答案解析
- 2026年及未来5年市场数据中国电视光盘重放设备行业市场全景评估及发展前景预测报告
- 2026年及未来5年市场数据中国民办初中行业市场发展数据监测及投资潜力预测报告
- 2025年高考数学全国一卷试题真题及答案详解(精校打印)
- 2025年中考一模卷(贵州)历史试题含答案解析
- 2024年河北省高考政治试卷(真题+答案)
- (高清版)DG∕TJ 08-2214-2024 道路照明工程建设技术标准
- 福州地铁笔试题库
- 10《我们爱和平》(教学设计)2023-2024学年统编版道德与法治六年级下册
- 2025年陕西中考试题道法及答案
- 《合成钻石及鉴定》课件
- 科学注塑专业知识培训
- 2024全国二卷语文高考试题
- 香港 雇佣 合同范例
评论
0/150
提交评论