一、消息映射原理 SDK中每个窗口类都有一个窗口过程,与之不同的是:所有MFC窗口都有一个默认的窗口过程。SDK中没有消息映射的概念,它有明确的回调函数,通过一个switch语句去判断收到的消息然后进行消息处理。
对于消息映射,这里有不同的几种猜想: 1、最直截了当的想法是、消息映射就是用一个数据结构把“消息”和“响应函数”串连起来,当窗口感知消息时,就对结构进行查找,找到相应的消息响应函数执行。但这方法也不能简单实现,因为,对于不同窗口类,同一消息有不同响应方式。同时对于同一消息,不同的窗口也会有不同的响应函数。 2、于是又出现一个可行方法,设计窗口基类(CWnd)时,我们让它对每种消息都来一个消息响应,并把消息响应函数定义为虚函数。但一个几乎什么也不做的CWnd类要有几百个“多余”的函数,哪怕这些消息响应函数都为纯虚函数,每个CWnd对象也要背负着一个巨大的虚拟表,这也是得不偿失的。 消息映射的目的:不是为是更加快捷地向窗口过程添加代码,而是一种机制的改变。 现在我们编写消息映射表,首先定义一个结构。至少包括两部分:1、消息ID 2、响应该消息的函数。如下:有了以上结构,我们就可以定义一个AFX_MESSAGE_ENTRY类型数组。用来容纳消息映射项。struct AFX_MSGMAP_ENTRY { UINT nMessage ; //感兴趣的消息 AFX_PMSpfn; //响应函数指针 }
当然,两个成员结构连接起来的消息映射表是不成熟的。Windows消息分为标准、命令、控件消息,每类消息包含数百不同ID、参数。要准确判别消息,于是再增加几个成员。
对于AFX_PMSG pfn,实际上等于作以下声明:void (CCmdTarget::*pfn)(); // 提示:AFX_PMSG为类型标识,具体声明是: typedef void(AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
pfn是一个不带参数和返回值的CCmdTarget类型函数指针,只能指向CCmdTarget类中不带参数和返回值的成员函数,这样pfn更为通用,但我们响应消息的函数许多需要传入参数的。为了解决这个矛盾,我们还要增加一个表示参数类型的成员。当然,还有其它……
最后确定消息映射表成员结构如下:struct AFX_MSGMAP_ENTRY { UINT nMessage; //Windows 消息ID UINT nCode; // 控制消息的通知码 UINT nID; //命令消息ID范围的起始值 UINT nLastID; //命令消息ID范围的终点 UINT nSig; // 消息的动作标识 AFX_PMSG pfn; };
AFX_MESSAGE_ENTRY _messageEntries[];
但这样还不够,对于每一个数组。只能保存当前感兴趣消息。只是我们想处理的一部分。一个MFC程序有多个窗口类。里面都应该有一个这样的 数组。 MFC还一个消息传递机制,即把自己不处理的消息给别的类。为了能查找各对象的消息映射表,我们还需要一个结构。把所有AFX_MESSAGE_ENTRY对象串联起来。struct AFX_MSGMAP { cons AFX_MSGMAP* pBaseMap; //指向别的类的AFX_MSGMA const AFX_MSGMAP_ENTRY* lpEntries; //指向自身的消息映射表 };
之后,在每个消息响应类中声明一个AFX_MSGMAP,让其中的pBaseMap指向基类或另一个类的messagemap。这让得到一个AFX_MSGMAP元素的单向链表,所有的消息映射形成了一张消息网。
当然,仅有消息映射表还不够,它只能把各个MFC对象的消息、参数与相应的消息响应函数连成一张网。为了方便查找,MFC在上面的类中插入了两个函数(其中theClass代表当前类):
一个是_GetBaseMessageMap(),用来得到基类的消息映射函数。原型如下:const AFX_MSGMAP* PASALtheClass::_GetBaseMessageMap() {return &baseCalss::messagemap;}
另一个是GetMessageMap(),用来得到自身消息映射的函数,原型如下:const AFX_MSGMAP* theClass::GetMssageMap()const {return &theClass::messagemap;}
有了消息映射表之后,我们得讨论到问题的关键,那就是消息发生以后,其对应的响应函数如何被调用。大家知道,所有的MFC窗口,都有一个同样的窗口过程——AfxWndProc(…) 在这里顺便要提一下的是,看过MFC源代码的朋友都知道,从AfxWndProc函数进去,会遇到一大堆曲折与迷团,因为对于这个庞大的消息映射机制,MFC要做的事情很多,如优化消息,增强兼容性等,这一大量的工作,有些甚至用汇编语言来完成,对此,我们很难深究它。所以我们要省略大量代码,理性地分析它。
对已定型的AfxWndProc来说,所有消息最多只能提供一种默认的处理方式。并不是我们想要的,从AfxWndProc下去,最终会调用一个函数OnWndMsg。LRESULT CALLBACK AfxWndProc(HWND hWnd,UINT hWnd,UINT nMsg,WPARAM wParam,LPARAM lParam) { ….. CWnd* pWnd=CWnd::FromHandlePermanet(hWnd);// 把对句柄的操作转换成对CWnd对象。 return AfxCallWndProc(pWnd,hWnd,nMsg,Wparam,lParam); }
把对句柄的操作转换成对CWnd对象是很重要的一件事,因为AfxWndProc只是一个全局函数,当然不知怎么样去处理各种windows窗口消息,所以它聪明地把处理权交给windows窗口所关联的MFC窗口对象。
现在,大家几乎可以想象得到AfxCallWndProc要做的事情,不错,它当中有一句:pWnd->WindowProc(nMsg,Wparam,lParam);
这样,MFC的窗口过程变成了自己的一个成员函数。WndowProc是一个虚函数,我们可以重写以响应不同消息。
WindowProc会调用到CWnd对象另一个成员函数OnWndMsg.原型如下:BOOL CWnd::OnWndMsg(UINT message,WPARAM wParam,LPARAM lParam,LRESULT* pResult) { if(message==WM_COMMAND) { OnCommand(wParam,lParam); …… } if(message==WM_NOTIFY) { OnCommand(wParam,lParam,&lResult); …… } const AFX_MSGMAP* pMessageMap; pMessageMap=GetMessageMap(); const AFX_MSGMAP_ENTRY* lpEntry; /*以下代码作用为:用AfxFindMessageEntry函数从消息入口pMessageMap处查找指定消息,如果找到,返回指定消息映射表成员的指针给lpEntry。然后执行该结构成员的pfn所指向的函数*/ if((lpEntry=AfxFindMessageEntry(pMessageMap->lpEntries,message,0,0)!=NULL) { lpEntry->pfn();
/*注意:真正MFC代码中没有用这一条语句。上面提到,不同的消息参数代表不同的意义和不同的消息响应函数有不同类型的返回值。而pfn是一个不带参数的函数指针,所以真正的MFC代码中,要根据对象lpEntry的消息的动作标识nSig给消息处理函数传递参数类型。这个过程包含很复杂的宏代换,大家在此知道:找到匹配消息,执行相应函数就行!*/ } }
二、MFC命令传递
从上面代码中,可以知道OnWndMsg能根据传进来的参数,找到匹配的消息及相应的消息响应。但平常响应消息的时候,原本属于框架窗口的WM_COMMAND消息,却可以放到视对象或文档对象中去响应。原理如下:
函数OnWndMsg原型中的代码:if(message==WM_COMMAND { OnCommand(wParam,lParam); …. }
即对于命令消息,实际上是交由OnCommand处理。再看:BOOL CFrameWnd::OnCommand(WPARAM wParam,LPARAM lParam) { ….. return CWnd:: OnCommand(wParam, lParam) }
可以看出,消息最终将由CWnd::OnCommand处理。再看:BOOL CWnd::OnCommand(WPARAM wParam,LPARAM lParam) { ….. return OnCmdMsg(nID,nCode,NULL,NULL); }
这里有一个多态性问题,虽然是执行CWnd类的函数,但因为这个函数在CFrameWnd::OnCmdMsg里执行,即当前指针是CFrameWnd类的指针。再者OnWndMsg是一个虚函数。所以如果CFrameWnd改写了OnCommand,程序会执行CFrameWnd::OnCmdMsg(…)。
对CFrameWnd::OnCmdMsg(…)函数的原理扼要分析如下:BOOL CFrameWnd:: OnCmdMsg(…) { CView pView = GetActiveView();//得到活动视指针。 if(pView-> OnCmdMsg(…)) return TRUE; //如果CView类对象或其派生类对象已经处理该消息,则返回。 ……//否则,同理向下执行,交给文档、框架、及应用程序执行自身的OnCmdMsg。 }
至此,CFrameWnd::OnCmdMsg完成了把WM_COMMAND消息传递到视、文档及应用对象实现消息响应。
三、MFC消息映射宏
现在我们看源代码,会感觉容易很多。
首先看DECLARE_MESSAGE_MAP()宏。它在MFC中定义如下;#define DECLARE_MESSAGE_MAP() private: staticconst AFX_MSGMAP_ENTRY _messageEntries[]; protected: static AFX_DATA const AFX_MSGMAP messagemap; virtualconst AFX_MSGMAP* GetMessageMap() const;
可以看出DECLARE_MESSAGE_MAP()定义了我们熟悉的两个结构和一个函数,显而易见,这个宏为每个需要实现消息映射的类提供了相关变量和函数。
现在集中精力来看一下BEGIN_MESSAGE_MAP,END_MESSAGE_MAP和ON_COMMAND三个宏,它们在MFC中定义如下(其中ON_COMMAND与另外两个宏并没有定义在同一个文件中,把它放到一起是为了好看):#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 ON_COMMAND(id, memberFxn) / { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn }, #define END_MESSAGE_MAP() / {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } / }; /
看似有点复杂,公式性的文字替换下。假设我们框架中有一菜单项为“Test”,即定义了如下宏:BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd) ON_COMMAND(ID_TEST,OnTest); END_MESSAGE_MAP()
展开后如下:
const AFX_MSGMAP* CMainFrame::GetMessageMap() const { return &CMainFrame::messageMap; } ///以下填入消息表映射信息 const AFX_MSGMAP CMainFrame::messageMap = { &CFrameWnd::messageMap, &CMainFrame::_messageEntries[0] }; //下面填入保存着当前类感兴趣的消息,可填入多个AFX_MSGMAP_ENTRY对象 const AFX_MSGMAP_ENTRY CMainFrame::_messageEntries[] = { { WM_COMMAND, CN_COMMAND, (WORD)ID_TEST, (WORD)ID_TEST, AfxSig_vv, (AFX_PMSG)&OnTest }, // 加入的ID_TEST消息参数 {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } //本类的消息映射的结束项 };
要完成ID_TEST消息映射,还要定义和实现OnTest响应函数。即在头文件中声明afx_msg void OnTest() 并在源文件中实现它。根据以上学的东西,我们知道ID_TEST命令发生,最终会执行OnTest函数。
http://www.jizhuomi.com/software/275.html