MFC消息映射机制实现

来源:互联网 发布:淘宝手机流量互刷软件 编辑:程序博客网 时间:2024/06/06 13:03

Windows程序的本质是依靠消息来维持运行的。每一个消息都有一个代码,并以WM_开头的常量表示。

MFC把消息分为三大类:

1.命令消息(WM_COMMAND:命令消息意味着“使用者命令程序做某些操作”。凡由UI对象产生的消息都是这种命令消息,可能来自菜单或加速键或工具栏按钮,并且都以WM_COMMAND呈现。

什么样的类有资格接受命令消息?凡派生自CCmdTarget的类皆有资格。从command target字面意义也可以看出来,这是命令消息的目的地。也就是说,凡派生自CCmdTarget者,它的骨子里就有了一种特殊的机制。

2.Control Notification:这种消息由控件产生,为的是向其父窗口(通常是对话框)通知某种情况。这种消息也是以WM_COMMAND形式呈现。

Windows9x后的新控件传送的是WM_NOTIFY,而旧有的控件为了兼容还是传送WM_COMMAND消息。

3.标准消息:除了前面说到的两类,任何以WM_开头的都可归于这一类。任何派生自CWnd的类均可接收此类消息。

当消息处理时,消息会循着Application Framework规定的路线,游走于各个对象之间,直到找到它的归宿(消息处理函数)。找不到的话,Framework最终就把它交给::DefWindowProc函数去处理。

“消息映射”是MFC內建的一个消息分派机制,只要利用数个宏以及固定形式的写法,就可以让Framework知道,一旦消息发生,该循哪一条路递送。每一个类只能拥有一个消息映射表,但也可以没有。

消息映射实现的标准操作:

在类声明中:

{    ……    DECLARE_MESSAGE_MAP()}

在类实现文件中:

BEGIN_MESSAGE_MAP(CMyWinApp, CWinApp)//{{AFX_MSG_MAP(CMFCApp)ON_COMMAND(ID_APP_ABOUT, OnAppAbout)//}}AFX_MSG_MAPEND_MESSAGE_MAP()

在BEGIN_和END_之中的宏,除了ON_COMMAND,还可以有许多种。标准的Windows消息并不需要我们指定处理函数的名称。因为标准函数的处理函数名也是“标准的”。

例如:

宏名-> ON_WM_CHAR,对应消息-> WM_CHAR,处理函数-> OnChar

即有,宏名的规则是在对应消息的前面加上ON_,处理函数则是在宏名的基础上去掉中间的_WM_并将单词中除了首字母外全变为小写即可。

上面用到的几个宏都定义于AFXWIN.H中,下面我们就来鉴赏一下吧。

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; \

其中static修饰词限定了数据的配置,使得每个“类”仅有一份数据,而不是每个“对象”各有一份数据。

宏中还包含了两个结构体,其定义如下:

struct AFX_MSGMAP_ENTRY{UINT 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 id'sUINT nSig;       // signature type (action) or pointer to message #AFX_PMSG pfn;    // routine to call (or special value)};

很明显,它的作用主要是将nMessage对应的消息与pfn对应的函数关联起来。其中AFX_PMSG的定义如下:

typedef void (AFX_MSG_CALL CWinThread::*AFX_PMSGT)(void);

对于AFX_MSG_CALL:

#ifndef AFX_MSG_CALL#define AFX_MSG_CALL#endif

即该宏也只是个占位符,没有具体内容。

另一个结构体:

struct AFX_MSGMAP{#ifdef _AFXDLLconst AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();//DLL工程中使用#elseconst AFX_MSGMAP* pBaseMap;#endifconst AFX_MSGMAP_ENTRY* lpEntries;};

暂不介绍DLL相关内容,所以第一个就不管了。pBaseMap是指向“基类消息映射表”的指针,它提供了一个走访整个继承链表的方法,有效地实现消息映射的继承性。

通过DECLARE_MESSAGE_MAP宏,我们相当于建立了如下的数据结构:

BEGIN.../ON.../END...

在AFXWIN.H中有:

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \{ return &baseClass::messageMap; } \const AFX_MSGMAP* theClass::GetMessageMap() const \{ return &theClass::messageMap; } \AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \{ &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \{ \

其中PASCAL表示stdcall的函数调用方式,AFX_COMDAT和AFX_DATADEF在此处只是一个占位符,没有其它意义。

#define END_MESSAGE_MAP() \{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \}; \

其中,AfxSig_end被定义为0;

在AFXMSG.H中又有:

#define ON_COMMAND(id, memberFxn) \{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },#define ON_WM_CREATE() \{ WM_CREATE, 0, 0, 0, AfxSig_is, \(AFX_PMSG)(AFX_PMSGW)(int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT))&OnCreate },#define ON_WM_DESTROY() \{ WM_DESTROY, 0, 0, 0, AfxSig_vv, \(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))&OnDestroy },#define ON_WM_PAINT() \{ WM_PAINT, 0, 0, 0, AfxSig_vv, \(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))&OnPaint },#define ON_WM_CLOSE() \{ WM_CLOSE, 0, 0, 0, AfxSig_vv, \(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))&OnClose },……

倘若我们有如下的宏:

BEGIN_MESSAGE_MAP(CMyView, CView)ON_WM_CREATE()ON_WM_PAINT()END_MESSAGE_MAP()

展开后将得到如下的代码:

const AFX_MSGMAP* PASCAL CMyView::_GetBaseMessageMap() { return & CView::messageMap; } const AFX_MSGMAP* CMyView::GetMessageMap() const { return & CMyView::messageMap; } const AFX_MSGMAP CMyView::messageMap = { & CMyView::_GetBaseMessageMap, & CMyView::_messageEntries[0] }; const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = {             { WM_CREATE, 0, 0, 0, AfxSig_is, (AFX_PMSG)(AFX_PMSGW)(int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT))&OnCreate },            { WM_PAINT, 0, 0, 0, AfxSig_vv, (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))&OnPaint },        {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } };

然后,我们就得到了如下的执行结果:



所有能够接收消息的类都应该派生自CCmdTarget,但是不一定派生自CCmdTarget类就能接收消息。其中CWinThread就是个特例。那么继承自CWinThread的CWinApp又该如何解释?看下面就能明白了:

//AFXWIN.H中class CWinApp : public CWinThread{DECLARE_DYNAMIC(CWinApp)……}//APPCORE.CPP中BEGIN_MESSAGE_MAP(CWinApp, CCmdTarget)//{{AFX_MSG_MAP(CWinApp)// Global File commandsON_COMMAND(ID_APP_EXIT, OnAppExit)// MRU - most recently used file menuON_UPDATE_COMMAND_UI(ID_FILE_MRU_FILE1, OnUpdateRecentFileMenu)ON_COMMAND_EX_RANGE(ID_FILE_MRU_FILE1, ID_FILE_MRU_FILE16, OnOpenRecentFile)//}}AFX_MSG_MAPEND_MESSAGE_MAP()

 是的,CWinApp是直接将messageMap中的pBaseMap指针指向了CCmdTarget。

如果我们“按照规矩”创建消息网,那么最后应该得到下图这样的情景:


如果BEGIN_MESSAGE_MAP宏中的两个参数没有按照规矩来写,消息可能会在不该流向某个类的时候流了过去,在应该被处理的时候却又跳离了。总之,情况会变得超出我们的控制。

综上所述,Message Map即可以说是一套宏,也可以说是宏展开后所代表的一套数据结构;甚至也可以说Message Map是一种操作,这个操作就是在我们所搭建的数据结构中查找与消息相吻合的项目,从而获得消息处理的函数指针。












1 0
原创粉丝点击