消息映射和命令传递是紧密相连的两部分。就像运行时类型识别和动态创建,后者都要在前者搭建的框架上执行。在消息映射和命令传递中,消息映射表的创建是基础,命令的传递路线是核心,让我跟随书中的足迹,记下自己学习中的心得。
消息映射(Message Mapping)的关键,是建立起一张消息攀爬的线路网,即消息映射表(Message Map)。以这张表为基础,将消息与表格中的元素比较,然后调用相应的处理程序,这种操作就是消息映射。
如果把消息映射表比作人体,我们可以大抵把它分成两部分,一部分是构成身体的骨骼,另一部分则是血肉。骨骼搭成框架,血肉使其生动,富有变化。消息映射表也是如此。整个表格就包含两个数据结构:AFX_MSGMAP和AFX_MSGMAP_ENTRY。其中,AFX_MSGMAP相当于骨骼,它构成了映射表的框架;AFX_MSGMAP_ENTRY好比血肉,依附于AFX_MSGMAP,构成整个映射表的元素内容。下面,让我们来认识一下这两个结构体:
struct AFX_MSGMAP
{
AFX_MSGMAP* pBaseMessageMap;
AFX_MSGMAP_ENTRY* lpEntries;
};
看到这里,我想大家就明白了,AFX_MSGMAP之所以能够搭成框架,是由于其中的成员是两个指针,它们各有各的用途。其中,AFX_MSGMAP_ENTRY就是我们要认识的第二个数据结构:
Struct AFX_MSGMAP_ENTRY
{
UINT nMessage;
UINT nCode;
UINT nID;
UINT nLastID;
UINT nSig;
AFX_PMSG pfn;
};
它记录了消息的内容和参数,作为比较的依据。
于是,每一个与消息相关的类中,都会存在这两个数据结构的对象。当然,它们必须是静态类型(static),以表明只属于类本身。如何把它们放到类中,就要依靠宏的本领了。引用侯捷先生的话,“他们并没有改变C++语言本身,也没有扩大语言的功能。他们只是设计了一些令人拍案叫绝的宏,而这些宏背后隐藏着巨大的机制”。这里体现出的,才刚刚开始,让我们见识一下吧。定义一个宏:
#define DECLARE_MESSAGE_MAP()
static AFX_MSGMAP_ENTRY _messageEntries[ ];
static AFX_MSGMAP messageMap;
virtual AFX_MSGMAP* GetMessageMap() const;
它们作为类的静态成员,就成为了所属类在消息映射表中的全权代表。于是,我们只需要在定义与消息有关的类时,把此宏放到类中,就OK了。代表有了,还有初始化的任务。
这两个数据结构的内容填塞工作又由三个宏完成:
#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_ENTRY theClass::_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}
};
这里的关键是ON_COMMAND宏。因为对这个宏作用的理解还不透彻,所以只引用侯捷先生的话,“我们还可以定义各种类似ON_COMMAND这样的宏,把各式各样的消息与特定的处理程序关联起来”。理解不够透彻,是指透过这个宏,自己难以把消息本身和它对应处理程序的联系搞清楚。当再次遇到功能类似的宏时,再来比对学习。
到这里,消息映射表的搭建与填充工作就告一段落了。每个与消息有关的类,都已由其代表AFX_MSGMAP和AFX_MSGMAP_ENTRY组成的元素在映射表中占据一席之地。各种不同的消息,如何在表中游走,与表中元素进行对比并调用对应的处理程序,这就是下篇 Command Routing中的内容。虽然框架已经搭好,但真正的好戏还在后头呢。