谁帮我解释一下这个函数 ( 积分: 20 )

  • 主题发起人 主题发起人 xiaoliang_001
  • 开始时间 开始时间
X

xiaoliang_001

Unregistered / Unconfirmed
GUEST, unregistred user!
delphi的system单元里的 procedure TObject.Dispatch(var Message)<br>谢谢!!!!!!!!!!!!!!!!!!
 
delphi的system单元里的 procedure TObject.Dispatch(var Message)<br>谢谢!!!!!!!!!!!!!!!!!!
 
(转)<br>⊙ TObject.Dispatch<br>===============================================================================<br>TObject.Dispatch 是个虚函数,它的声明如下:<br><br> &nbsp;procedure TObject.Dispatch(var Message); virtual;<br><br>请注意它的参数虽然与 MainWndProc 和 WndProc 的参数相似,但它没有规定参数的类型。这就是说,Dispatch 可以接受任何形式的参数。<br><br>Delphi 的文档指出:Message参数的前 2 个字节是 Message 的 ID(下文简称为 MsgID),通过 MsgID 搜索对象的消息处理方法。<br><br>这段话并没有为我们理解 Dispatch 方法提供更多的帮助,看来我们必须通过阅读源代码来分析这个函数的运作过程。<br><br>TObject.Dispatch 虽然是个虚方法,但却没有被 TPersistent、TComponent、TControl、TWinControl、TForm 等后续类重载( TCommonDialog 调用了 TObject.Dispatch,但对于整个 VCL 消息系统并不重要),并且只由 TControl.WndProc 调用过。所以可以简单地认为如果消息没有在 WndProc 中被处理,则被 TObject.Dispatch 处理。<br><br>我们很容易查觉到一个很重要的问题:MsgID 是 2 个字节,而 TMessage.Msg 是 4 个字节,如果 TControl.WndProc 把 TMessage 消息传递给 Dispatch 方法,是不是会形成错误的消息呢?<br><br>要解释这个问题,必须先了解 Windows 消息的规则。由于 Windows 操作系统的所有窗口都使用消息传递事件和信息,Microsoft 必须制定窗口消息的格式。如果每个程序员都随意定义消息 ID 值肯定会产生混乱。Microsoft 把窗口消息分为五个区段:<br><br> &nbsp;0x00000000 至 WM_USER - 1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 标准视窗消息,以 WM_ 为前缀<br> &nbsp;WM_USER &nbsp; &nbsp;至 WM_APP &nbsp;- 1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 用户自定义窗口类的消息<br> &nbsp;WM_APP &nbsp; &nbsp; 至 0x0000BFFF &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;应用程序级的消息<br> &nbsp;0x0000C000 至 0x0000FFFF &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;RegisterWindowMessage 生成的消息范围<br> &nbsp;0x00010000 至 0xFFFFFFFF &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Microsoft 保留的消息,只由系统使用<br><br> &nbsp;( WM_USER = 0x00000400, &nbsp;WM_APP = 0x00008000 )<br><br>发现问题的答案了吗?原来应用程序真正可用的消息只有 0x00000000 至 0x0000FFFF,也就是消息 ID 只有低位 2 字节是有效的。(Borland 真是牛啊,连这也能想出来。)<br><br>由于 Intel CPU 的内存存放规则是高位字节存放在高地址,低位字节存放在低地址,所以 Dispatch 的 Message 参数的第一个内存字节就是 LoWord(Message.Msg)。下图是 Message参数的内存存放方式描述:<br><br> &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp;| + Memory<br> &nbsp; &nbsp; &nbsp; &nbsp;|--------|<br> &nbsp; &nbsp; &nbsp; &nbsp;| HiWord |<br> &nbsp; &nbsp; &nbsp; &nbsp;|--------|<br> &nbsp; &nbsp; &nbsp; &nbsp;| LoWord | &lt;-- [EDX]<br> &nbsp; &nbsp; &nbsp; &nbsp;|--------|<br> &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp;|<br> &nbsp; &nbsp; &nbsp; &nbsp;|--------|<br> &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp;|<br> &nbsp; &nbsp; &nbsp; &nbsp;|--------| - Memory<br> &nbsp; &nbsp; &nbsp; &nbsp;[ 图示:Integer 类型的 MsgID 在内存中的分配(见 Dispatch 汇编代码) ]<br> &nbsp; &nbsp; &nbsp; &nbsp;(为了简单起见,我用 Word 为内存单位而不是 Byte,希望不至于更难看懂)<br><br>现在可以开始阅读 TObject.Dispatch 的汇编代码了(不懂汇编没关系,后面会介绍具体的功能):<br><br>procedure TObject.Dispatch(var Message); virtual; <br>asm<br> &nbsp; &nbsp;PUSH &nbsp; &nbsp;ESI &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; 保存 ESI<br> &nbsp; &nbsp;MOV &nbsp; &nbsp; SI,[EDX] &nbsp; &nbsp; &nbsp; ; 把 MsgID 移入 SI (2 bytes)<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; 如果 MsgID 是Integer 类型,[EDX] = LoWord(MsgID),<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; 见上图<br> &nbsp; &nbsp;OR &nbsp; &nbsp; &nbsp;SI,SI &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp;JE &nbsp; &nbsp; &nbsp;@@default &nbsp; &nbsp; &nbsp;; 如果 SI = 0,调用 DefaultHanlder<br> &nbsp; &nbsp;CMP &nbsp; &nbsp; SI,0C000H<br> &nbsp; &nbsp;JAE &nbsp; &nbsp; @@default &nbsp; &nbsp; &nbsp;; 如果 SI &gt;= $C000,调用 DefaultHandler (注意这里)<br> &nbsp; &nbsp;PUSH &nbsp; &nbsp;EAX &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; 保存对象的指针<br> &nbsp; &nbsp;MOV &nbsp; &nbsp; EAX,[EAX] &nbsp; &nbsp; &nbsp;; 找到对象的 VMT 指针<br> &nbsp; &nbsp;CALL &nbsp; &nbsp;GetDynaMethod &nbsp;; 调用对象的动态方法; 如果找到了动态方法 ZF = 0 ,<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; 没找到 ZF = 1<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; 注:GetDynaMethod 是 System.pas 中的获得动态方法地<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; 址的汇编函数<br> &nbsp; &nbsp;POP &nbsp; &nbsp; EAX &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; 恢复 EAX 为对象的指针<br> &nbsp; &nbsp;JE &nbsp; &nbsp; &nbsp;@@default &nbsp; &nbsp; &nbsp;; 如果没找到相关的动态方法,调用 DefaultHandler &nbsp; &nbsp; <br> &nbsp; &nbsp;MOV &nbsp; &nbsp; ECX,ESI &nbsp; &nbsp; &nbsp; &nbsp;; 把找到的动态方法指针存入 ECX<br> &nbsp; &nbsp;POP &nbsp; &nbsp; ESI &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; 恢复 ESI<br> &nbsp; &nbsp;JMP &nbsp; &nbsp; ECX &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; 调用对象的动态方法<br><br>@@default:<br> &nbsp; &nbsp;POP &nbsp; &nbsp; ESI &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; 恢复 ESI<br> &nbsp; &nbsp;MOV &nbsp; &nbsp; ECX,[EAX] &nbsp; &nbsp; &nbsp;; 把对象的 VMT 指针存入 ECX,以调用 DefaultHandler<br> &nbsp; &nbsp;JMP &nbsp; &nbsp; DWORD PTR [ECX] + VMTOFFSET TObject.DefaultHandler<br>end;<br><br>TObject.Dispatch 的执行过程是:<br> &nbsp; &nbsp;把 MsgID 存入 SI,作为动态方法的索引值<br> &nbsp; &nbsp;如果 SI &gt;= $C000,则调用 DefaultHandler(也就是所有 RegisterWindowMessage<br> &nbsp; &nbsp; &nbsp; &nbsp;生成的消息ID 会直接被发送到 DefaultHandler 中,后面会讲一个实例)<br> &nbsp; &nbsp;检查是否有相对应的动态方法<br> &nbsp; &nbsp;找到了动态方法,则执行该方法<br> &nbsp; &nbsp;没找到动态方法,则调用 DefaultHandler<br><br>原来以 message 关键字定义的对象方法就是动态方法,随便从 TWinControl 中抓几个消息处理函数出来:<br><br> &nbsp; &nbsp;procedure WMSize(var Message: TWMSize); message WM_SIZE;<br> &nbsp; &nbsp;procedure WMMove(var Message: TWMMove); message WM_MOVE;<br><br>到现在终于明白 WM_SIZE、WM_PAINT 方法的处理过程了吧。不但是 Windows 消息,连 Delphi 自己定义的消息也是以同样的方式处理的:<br><br> &nbsp; &nbsp;procedure CMEnabledChanged(var Message: TMessage); message CM_ENABLEDCHANGED;<br> &nbsp; &nbsp;procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;<br><br>所以如果你自己针对某个控件定义了一个消息,你也可以用 message 关键字定义处理该方法的函数,VCL 的消息系统会自动调用到你定义的函数。<br><br>由于 Dispatch 的参数只以最前 2 个字节为索引,并且自 MainWndProc 到 WndProc 到 Dispatch 都是以引用(传递地址)的方式来传递消息内容,你可以将消息的结构设置为任何结构,甚至可以只有 MsgID —— 只要你在处理消息的函数中正确地访问这些参数就行。
 
好东西收藏!<br><br>李维的《Inside VCL》里面讲的非常详细!
 
后退
顶部