MFC消息映射机制.doc

上传人:枫** 文档编号:542125903 上传时间:2023-04-13 格式:DOC 页数:11 大小:87KB
返回 下载 相关 举报
MFC消息映射机制.doc_第1页
第1页 / 共11页
MFC消息映射机制.doc_第2页
第2页 / 共11页
MFC消息映射机制.doc_第3页
第3页 / 共11页
MFC消息映射机制.doc_第4页
第4页 / 共11页
MFC消息映射机制.doc_第5页
第5页 / 共11页
点击查看更多>>
资源描述

《MFC消息映射机制.doc》由会员分享,可在线阅读,更多相关《MFC消息映射机制.doc(11页珍藏版)》请在金锄头文库上搜索。

1、由于工作需要,这几天学了一点MFC,在AFX里看到很多熟悉的东西,如类型信息,序列化,窗口封装和消息分派。几乎每个界面库都必须提供这些基础服务,但提供的手法却千差万别。MFC大量地借用了宏,映射表来实现,而VCL则更多的在语言级别上给与支持。这其实是很容易理解的,因为C+是一个标准,不会因某个应用而随便扩展语言;相反Delphi完全由一个公司掌握,因此每支持一项新技术,变化最大的往往是语言本身。学习MFC的代码,再对照VCL的实现,这真是一个很有意思的过程,其中可以看到两个框架在一些设计思想上是殊途同归的,所不同的是表现手法,以及封装的程度。我计划将这段时间阅读MFC的心得写成一系列文章,其中

2、可能会穿插与VCL的对比,不管你熟悉VCL还是MFC,通过这些文章或许可从另一个角度来看待自己熟悉的框架。这是第一篇:消息分派。消息处理函数表 MFC和VCL在对消息进行封装的时候,都没有使用虚函数机制。原因是虚函数带来了不必要的空间开销。那么它们用什么来代替虚函数,即可减少空间浪费,也可实现类似虚函数的多态呢?让我们从一个例子开始。假设父类ParentWnd处理了100个消息,并且将这100个处理函数声明为虚函数;此时有一个子类ChildWnd它只需要处理2个消息,另外98个交由ParentWnd默认处理,但是ChildWnd的虚表仍然占有100个函数指针,其中2个指向自己的实现,另外98个

3、指向父类的实现,情况大概像下面这样:指向父类实现的函数指针浪费了空间,当控件类非常多的时候,这种浪费就非常明显。因此必须走另一条路,将不必要的函数指针去掉,如下图所示: ChildWnd去掉函数指针之后,当有一个消息需要Fun100处理时,ChildWnd就束手无策了。需要一个方法让ChildWnd能够找到父类的表,我们再对这个数据结构进行改进如下:现在看来好多了,如果ChildWnd有一个消息需要Fun1处理,则查找ChildWnd的MsgHandlers,找到Fun1函数指针调用之;如果需要Fun100处理,发现ChildWnd的MsgHandlers没有Fun100,则通过ParentT

4、able找到父类的MsgHandlers继续查找。如此一直查找,到最后再找不到,就调用DefWindowProc作默认处理。 MFC和VCL都是通过类似的方法实现消息分派的。只是VCL有编译器的支持,直接将这个表放到VMT中,因此实现起来非常简单,只需在控件类里作如下声明:procedure WMMButtonDown(var Message: TWMMButtonDown); message WM_MBUTTONDOWN;TObject.Dispatch会将WM_MBUTTONDOWN正确分派到WMMButtonDown。 MFC就没有这么简单,它需要手工去构建这个表,如果一个类想处理消息,

5、它必须声明一些结构和函数,代码类似这样:struct AFX_MSGMAP_ENTRY UINT nMessage; /消息 AFX_PMSG pfn; /消息处理函数;struct AFX_MSGMAP const AFX_MSGMAP* pBaseMap; /指向基类的消息映射 const AFX_MSGMAP_ENTRY* lpEntries; /消息映射表;class CMFCTestView : public CViewprotected: void OnLButtonDown(UINT nFlags, CPoint point);private: static const AFX_

6、MSGMAP_ENTRY _messageEntries;protected: static const AFX_MSGMAP messageMap; virtual const AFX_MSGMAP* GetMessageMap() const;仔细看_messageEntries和messageMap的声明,是不是和上面的图非常相似,接下来看看实现部分如何初始化这个表:const AFX_MSGMAP* CMFCTestView:GetMessageMap() const return & CMFCTestView:messageMap; const AFX_MSGMAP CMFCTest

7、View:messageMap = & CView:messageMap, & CMFCTestView:_messageEntries0 ;const AFX_MSGMAP_ENTRY CMFCTestView:_messageEntries = WM_LBUTTONDOWN, (AFX_PMSG)&OnLButtonDown 0, (AFX_PMSG)0 ;void CMFCTestView:OnLButtonDown(UINT nFlags, CPoint point) CView:OnLButtonDown(nFlags, point); messageMap的第一个成员指向其父类的m

8、essageMap,即&CView:messageMap;第二个成员则指向下面的消息映射表;GetMessageMap是一个虚函数,显然是为了父类分派消息的时候能够找到正确的消息映射结构,后面会看到这一点。 _messageEntries数组为消息映射表,第一个元素处理WM_LBUTTONDOWN消息,其处理函数是OnLButtonDown,这个函数在CMFCTestView声明和实现;第二个元素标识了映射表的结尾。现在,你想处理什么消息,都可以往_messageEntries里加新的元素并指定你的处理函数,只是如果每一个类都需要手工写这些代码,那将是很繁琐的事情;幸好C+有宏,可以用宏来作一

9、些简化的代码,先将消息映射的声明用DECLARE_MESSAGE_MAP()表示,则类声明变成下面这样:class CMFCTestView : public CViewDECLARE_MESSAGE_MAP();而实现部分,变成了下面这样:BEGIN_MESSAGE_MAP(CMFCTestView, CView) ON_WM_LBUTTONDOWN()END_MESSAGE_MAP()这就是MFC的消息映射宏,实际的代码和这里有一些出入,不过大体是差不多的,其核心作用就是构造消息处理函数表。现在打开VC去看看那几个宏,是不是觉得其实很简单。建好消息映射表后,接下来要看看消息如何流到指定的处

10、理函数里。消息流向我们可以认为消息的最初进入点是CWnd:WindowProc,在以后的文章会说明消息如何流到这个函数。CWnd是所有窗口类的基类,CMFCTestView当然也是CWnd的子孙类。WindowProc的代码很简单,调用OnWndMsg进行消息分派,如果没有处理函数,则调用DefWindowProc作默认处理:LRESULT CWnd:WindowProc(UINT message, WPARAM wParam, LPARAM lParam) / OnWndMsg does most of the work, except for DefWindowProc call LRES

11、ULT lResult = 0; if (!OnWndMsg(message, wParam, lParam, &lResult) lResult =DefWindowProc(message, wParam, lParam); return lResult;最重要的是OnWndMsg成员函数,我将里面的代码作了简化,去掉了命令通知消息和一些优化代码,最终的代码如下:BOOL CWnd:OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult) LRESULT lResult = 0; const AFX_MSG

12、MAP* pMessageMap; /取得消息映射结构,GetMessageMap为虚函数,所以实际取的是CMFCTestView的消息映射pMessageMap = GetMessageMap();/查找对应的消息处理函数 for (pMessageMap != NULL; pMessageMap = pMessageMap-pBaseMap) if (message lpEntries, message, 0, 0) != NULL) goto LDispatch; . .LDispatch: /通过联合来匹配正确的函数指针类型 union MessageMapFunctions mmf;

13、 mmf.pfn = lpEntry-pfn; int nSig; nSig = lpEntry-nSig; /nSig代表函数指针的类型,通过一个大大的Case来匹配。 switch (nSig) case AfxSig_bD: lResult = (this-*mmf.pfn_bD)(CDC:FromHandle(HDC)wParam); break; . . case AfxSig_vwp: CPoint point(DWORD)lParam); (this-*mmf.pfn_vwp)(wParam, point); break; return TRUE;在代码中看到GetMessage

14、Map的调用,根据前面的分析已经知道这是一个虚函数,所以实际调用到的是CMFCTestView:GetMessageMap(),也就是这里取到了CMFCTestView的消息映射结构。接下来根据当前的消息表寻找对应的消息处理函数,调用AfxFindMessageEntry查找消息映射表,如果找不到就继续到基类去找,这就是For循环做的事情。AfxFindMessageEntry使用内嵌汇编来提高查找的效率,VCL也是这样做的。如果找到处理函数,则调用goto LDispatch;跳到下面的Case语句,根据nSig判断函数指针的实际类型,最后转换并调用之,此时我们的OnLButtonDown函数就被调用到了。我们看到了Goto语句的使用,也看到下面大大的Case,这都是OO设计的禁忌,特别是下面的Case,为什么要用Case呢?这是由

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 生活休闲 > 社会民生

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号