MFC消息机制---消息映射

Win32程序消息流动


在讲MFC消息机制之前,先来介绍一下Win32程序的消息处理。

Win32程序的编写流程一般就分为:注册窗口,创建窗口,显示窗口,更新窗口,

消息循环,消息处理。

Win32程序执行起来后,会在如下代码进行消息循环:

while(GetMessage(&msg...)) {//获取消息
TranslateMessage(...);//翻译消息
DispatchMessage(...);//派发消息
}


GetMessage获取的消息来自两种途径:

1.      系统消息队列:鼠标键盘等消息进入系统消息队列。

2.      用户消息队列:用户程序触发的事件消息

DispatchMessage会把消息派发到消息处理函数WndProc由我们处理

其中,我们可以通过PostMessage函数将消息投送至用户消息队列,通过SendMessage将消息直接投递至WndProc函数处理。

MFC消息映射

(下面我们用模拟的方式讲解MFC的消息映射,相关的宏与函数与MFC中不完全一样,我去掉了与消息映射无关的东西,这里我们只关注消息映射)

 

Windows 程序靠消息的流动而维护生命。我们已经在上面看到了消息的一般处理方式,

也就是在窗口函数中借着一个大大的switch/case 比对动作,判别消息再调用对应的处理

例程。为了让大大的switch/case 比对动作简化,也让程序代码更模块化一些,MFC

提供了一个消息映射表作法,把消息和其处理例程关联起来。

当我们的类别库成立,如果其中与消息有关的类别(姑且叫作「消息标的类别」好了,

在MFC 之中就是CCmdTarget)都是一条鞭式地继承,我们应该为每一个「消息标的类

别」准备一个消息映射表,并且将基础类别与衍生类别之消息映射表串接起来。然后,

当窗口函数做消息的比对时,我们就可以想办法导引它沿着这条路走过去,

但是,MFC 之中用来处理消息的C++ 类别,并不呈单鞭发展。作为application framework

的重要架构之一的document/view,也具有处理消息的能力(你现在可能还不清楚什么是

document/view,没有关系)。因此,消息藉以攀爬的路线应该有横流的机会。

消息如何流动,我们暂时先不管。是直线前进,或是中途换跑道,我们都暂时不管,我们先

把这个攀爬路线网讲明白。这整个攀爬路线网就是所谓的消息映射表(Message Map);说它是一张地图,当然也没有错。将消息与表格中的元素比对,然后调用对应的处理例程,这种动作我们也称之为消息映射(Message Mapping)。

消息映射表

AFX_MSGMAP和AFX_MSGMAP_ENTRY

消息映射表的建立用到了两个重要的数据结构:

struct AFX_MSGMAP
{
AFX_MSGMAP* pBaseMessageMap;
AFX_MSGMAP_ENTRY* lpEntries;
};

AFX_MSGMAP可以理解为消息映射表的一个节点。其中pBaseMessageMap成员变量保存上一个节点的地址,lpEntries为消息映射表节点的数据,它将指向一个数组首地址,数组中每一个元素为AFX_MSGMAP_ENTRY,这时大家可能已经猜到了,AFX_MSGMAP_ENTRY这个结构体肯定有成员变量为消息ID和消息处理函数,这样就实现了消息和处理函数的映射。

struct AFX_MSGMAP_ENTRY // MFC 4.0 format
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFYcode
UINT nID; // control ID (or 0 for windowsmessages)
UINT nLastID; // used for entriesspecifying a range of control id's
UINT nSig; // signature type (action) orpointer to message #
AFX_PMSG pfn; // routine to call (orspecial value)
};

AFX_MSGMAP_ENTRY结构包含了

一个消息的所有相关信息,其中

nMessage为Windows消息的ID号

nCode为控制消息的通知码

nID为Windows控制消息的ID

nLastID表示如果是一个指定范围的消息被映射的话,nLastID用来表示它的范围。

nSig表示消息的动作标识

AFX_PMSG pfn 它实际上是一个指向和该消息相应的执行函数的指针。

 

AFX_PMSG定义为:

typedef void (CCmdTarget::*AFX_PMSG)(void);

重要的宏

有了上面两个结构体,我们可以在每个类中声明一个AFX_MSGMAP_ENTRY数组,用来存储该类要接收的消息和该类中的处理函数的映射关系,然后再声明一个AFX_MSGMAP成员变量,用来指向这个数组和该类的父类的AFX_MSGMAP成员变量,这样就把每个类串起来了。

上面说的这些工作,都由这几个宏来完成:

DECLARE_MESSAGE_MAP和BEGIN_MESSAGE_MAP、END_MESSAGE_MAP

我们来这样定义宏:

#defineDECLARE_MESSAGE_MAP() \
staticAFX_MSGMAP_ENTRY _messageEntries[]; \
static AFX_MSGMAPmessageMap; \
virtual AFX_MSGMAP*GetMessageMap() const;


于是,DECLARE_MESSAGE_MAP就相当于声明了这样一个数据结构:


这个数据结构的内容填塞工作由三个宏完成:

#define BEGIN_MESSAGE_MAP(theClass,baseClass) \
AFX_MSGMAP* theClass::GetMessageMap() const\
{ return &theClass::messageMap; } \
AFX_MSGMAP theClass::messageMap = \
{ &(baseClass::messageMap), \
(AFX_MSGMAP_ENTRY*)&(theClass::_messageEntries) }; \
AFX_MSGMAP_ENTRYtheClass::_messageEntries[] = \
{
#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, 0, (WORD)id, (WORD)id,AfxSig_vv, (AFX_PMSG)memberFxn },
#define END_MESSAGE_MAP() \
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
};

其中的AfxSig_end 定义为:

enum AfxSig
{
AfxSig_end = 0, // [marks end of messagemap]
AfxSig_vv,
};

注:AfxSig_xx 用来描述消息处理例程memberFxn 的类型(参数与回返值)。

END_MESSAGE_MAP中有一个{ 0, 0,0, 0, AfxSig_end, (AFX_PMSG)0 },它的作用是在消息进行匹配的时候,作为匹配结束标识。

 

我们以CView 为例来演示一下这几个宏的用法:

// in header file
class CView : public CWnd
{
public:
...
DECLARE_MESSAGE_MAP()
};
// in implementation file
#define CViewid 122
...
BEGIN_MESSAGE_MAP(CView, CWnd)
ON_COMMAND(CViewid, 0)
END_MESSAGE_MAP()<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

将宏展开我们就清晰了:

// in header file
class CView : public CWnd
{
public:
...
static AFX_MSGMAP_ENTRY _messageEntries[];
static AFX_MSGMAP messageMap;
virtual AFX_MSGMAP* GetMessageMap() const;
};
// in implementation file
AFX_MSGMAP* CView::GetMessageMap() const
{ return &CView::messageMap; }
AFX_MSGMAP CView::messageMap =
{ &(CWnd::messageMap),
(AFX_MSGMAP_ENTRY*)&(CView::_messageEntries) };
AFX_MSGMAP_ENTRY CView::_messageEntries[] =
{
{ WM_COMMAND, 0, (WORD)122, (WORD)122, 1,(AFX_PMSG)0 },
{ 0, 0, 0, 0, 0, (AFX_PMSG)0 }
};

用图表示为:

 

只要在每个我们想处理消息的类中增加这几个宏,我们的消息就可以很方便的顺着基类向上传递了。

 

普通消息(COMMAND消息会有横向传递)的传递的过程就是从子类找寻匹配的处理函数,没有则向上传递去父类找寻,我们可以写一个这样的函数(实际这个函数在CCmdTarget类中的OnCmdMsg函数中有,用来找寻匹配COMMAND消息;在CWnd::WindowProc中也有,用来找寻匹配非普通消息)

//传递一个AFX_MSGMAP,它会纵向传递
void MsgMapPrinting(AFX_MSGMAP*pMessageMap)
{
//纵向传递,每到达一层,调用printlpEntries去找寻匹配
for(; pMessageMap != NULL; pMessageMap =pMessageMap->pBaseMessageMap)
{
AFX_MSGMAP_ENTRY* lpEntry =pMessageMap->lpEntries;
printlpEntries(lpEntry);//这里我们不模拟找寻匹配,只是输出一个值,表示我们来过
}
}
void printlpEntries(AFX_MSGMAP_ENTRY*lpEntry)
{
struct {
int classid;
char* classname;
} classinfo[] = {
CCmdTargetid, "CCmdTarget",
CWinThreadid, "CWinThread",
CWinAppid, "CWinApp",
CMyWinAppid, "CMyWinApp",
CWndid, "CWnd",
CFrameWndid, "CFrameWnd",
CMyFrameWndid, "CMyFrameWnd",
CViewid, "CView",
CMyViewid, "CMyView",
CDocumentid, "CDocument",
CMyDocid, "CMyDoc",
0, " "
};
for (int i=0; classinfo[i].classid != 0;i++)
{
if (classinfo[i].classid ==lpEntry->nID)
{
cout << lpEntry->nID <<" ";
cout << classinfo[i].classname<< endl;
break;
}
}
}


  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Barry__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值