@会网络的老鼠

涂飞平的博客空间

VC++与Delphi消息机制比较[原]

10 年前 0

使用过delphi和vc++的朋友对于两者强大的基础开发能力赞叹不已。而对基础(底层)开发最大的表现就是对于API的快速引入和良好的消息机制。两者都有完善的类库可以避免太多直接使用API,当然两者都可以抛开类库直接使用API来进行开发。
两个开发工具在API的支持方面采用的方法是一样的,都是使用编译器级别的支持(它们的编译/连接器都可以根据PE格式正确导入需要的API)。而在消息机制的实现方面,两个工具采用的方式却不完全一样。
delphi和vc++都是面向对象开发工具,在对象如何关联到消息这部分,两者采用的思想是大同小异的,都避免了直接使用虚拟函数,因为消息实在太多了(还有很多自定义消息),使用虚拟函数处理消息的开销太大了。而在具体机制的实现上,两者还是有很大的区别的:vc采用的是消息映射表格方式将消息和处理消息的例程对应起来,而且实现起来很简单,并没有在编译器层面浪费任何开销,整个映射过程对于开发者是透明的;而delphi采用的方式虽然也是消息映射表格,但是它为了简单,直接将表格交给编译器去完成(DMT),并且整个过程对于开发者是不透明的。
下面分别简述一下两者处理方式。
在delphi中增加一个消息处理函数,你可以使用下面的方式定义一个函数:

  
procedure TMyClass.WMMYCOMMAND(var Message:TMessage);message WM_MYCOMMAND;
然后是实现部分:
  procedure TMyClass.WMMYCOMMAND(var Message:TMessage);
begin

end;

delphi编译器看到message关键字,就会将这个方法地址和方法对应的消息id放置到TMyClass的DMT(动态方法表)中,当消息进入到WndProc中,默认的处理(inherited关键字的作用除了找父类的WndProc,也会查找对应的DMT)会通过VMT中的DMT域查找其中的消息ID并找到对应的消息处理例程,如果没有找到方法,还会通过VMT中的Parent找到父类的VMT并再次重复这个过程,达到遍历所有继承关系的消息处理链。这些处理过程是采用代码完成的(VCL源代码中有,为了效率考虑,相关部分是使用汇编完成的),但是DMT的生成和DMT的格式与VCL无关,它是由delphi的编译器直接决定的。
在VC++中增加一个消息处理函数,你应该作如下声明:
DECLARE_MESSAGE_MAP()//默认的情况下VC已经生成,如果没有生成的话,记得自己手工加入。
然后在类的实现部分还要继续加入你自己的处理例程:
BEGIN_MESSAGE_MAP(sample,CFrameWnd)
//这个宏填写两个参数,一个子类,一个父类
ON_MESSAGE(WM_MYCOMMAND,OnMyCommand) //ON_MESSAGE也是一个宏,其实就是一个表格的一个项目,下面有定义原形
END_MESSAGE_MAP()//结束宏
然后实现例程:
  LRESULT CMyWnd::OnMyCommand(int WPARAM,int LPARAM)
{

}

下面是几个宏定义:
  #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;
//定义好需要的函数和数组,便于后面使用。

#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_DATADEF const AFX_MSGMAP theClass::messageMap =
{ &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] };
const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
{
//实现上面定义的函数并准备初始化数组,数组是AFX_MSGMAP_ENTRY类型的数组,而中间的过程就是上面例子中ON_MESSAGE宏做
//的事情,通过下面的宏定义就知道,纯粹是一个AFX_MSGMAP_ENTRY结构的初始化而已。

#define END_MESSAGE_MAP()
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
//最后以一个全0的数组作为结束,MFC搜索的时候遇到全0的结构就退出遍历的过程。

#define ON_MESSAGE(message, memberFxn)
{ message, 0, 0, 0, AfxSig_lwl,
(AFX_PMSG)(AFX_PMSGW)(LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM))&memberFxn },
//AfxSig_lwl其实是函数原形定义的标志,通过这个参数,MFC会正确调用处理例程的

通过上面的分析,我们发现,当遇到消息处理的时候,MFC最后也是遍历消息映射表,默认处理也会通过_GetBaseMessageMap方法查询父类的映射表。然后调用处理例程。不过这个过程我们可以看到全部的实现过程,并没有使用任何扩展的编译器技术,采用的都是一些简单的技术和数据结构而已。这对于我们理解MFC消息机制是一个好的帮助。delphi在消息处理编程方面显得更加简单方便,但其实使用了一些更加隐秘的技术细节。
先写这么多!晚安!!!

编写评论