MFC消息映射机制实现

17 篇文章 1 订阅
9 篇文章 6 订阅

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_MAP
END_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 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)
};

很明显,它的作用主要是将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 _AFXDLL
	const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();//DLL工程中使用
#else
	const AFX_MSGMAP* pBaseMap;
#endif
	const 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 commands
	ON_COMMAND(ID_APP_EXIT, OnAppExit)
	// MRU - most recently used file menu
	ON_UPDATE_COMMAND_UI(ID_FILE_MRU_FILE1, OnUpdateRecentFileMenu)
	ON_COMMAND_EX_RANGE(ID_FILE_MRU_FILE1, ID_FILE_MRU_FILE16, OnOpenRecentFile)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

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

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


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

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












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值