会员: 密码:  免费注册 | 忘记密码 | 会员登录 网页功能: 加入收藏 设为首页 网站搜索  
技术文档 > Delphi
VCL源码分析方法的一点体会
发表日期:2004-06-20 01:04:09作者:c 出处:http://www.delphibbs.com  

主  题:  谈谈我对VCL源码分析方法的一点体会,欢迎大家进来看看,并发表自己的心得和看法!
作  者:  cg1120 (代码最优化-§惟坚韧者始能遂其志§)
    最近一段时间似乎流行源码分析:)我也来谈谈在过去一段时间里对VCL源码的分析方法方面的一点体会,本文将不探讨
    VCL类库的构架和设计模式方面的东本,只是以我们常见的控件属性/方法的实现过程作简单的说明,希望对初学者有所帮助
VCL分析方法
例:TButton.Caption属性的由来
(本文仅以此献给DELPHI初学者)
    用过一段时间DELPHI的朋友,都会对VCL源码感兴趣。本人也常常在各大论坛见到一些网友研究讨论过关于VCL源码的贴子。
    不过,很多网友很努力的想看懂,可最后还是半途而废,因为他们总是理不出个头绪、看得云里雾里。笔者我也有看源码的习惯,
    没事的时候就点点鼠标右键,总是希望得到一些侥幸的收获和开发技巧。
    不过万事都得先有个基本前题,就像人上学的过程一样(这里指正常人)要按部就班的来,一般不可能小学一毕业就直接去念
大学,除非他(她)是个天才或经过特别培训。所以各位GGJJDDMM,看VCL源码也是有个基本前题的,首先你得熟悉WIN32 API/SDK,
如果你说不知道的话,可以参考书籍《Programming Windows》(中文名《WINDOWS 程序设计》)。其次是你应当对Object Pascal比
较熟悉,或者你曾经对DELPHI的组件进行过扩展(做过组件开发),那么我相信你对Object Pascal已经熟悉。不熟也不要紧,DELPHI
的在线帮助就有对Object Pascal的讲述,如果英文太差也不要紧,网上也有很多热心网友翻译过来的中文帮助和语言参考书。
呵呵,本人写技术文章就像在写散文:)
    言归正传,我们这篇文章的主题是对VCL源码的分析,分析当然有一个分析方法的问题,总不能随便打开一个源程序,逮着一个
    函数就分析一个函数吧:)所以我们也应该有选择,有目的的分析。
想想我们每天编码时都会遇到的属性有哪些?呵呵,NAME,CAPTION,VISIBLE,还有一些控件的TEXT(如EDIT1.TEXT)。那么我们就
以控件的CAPTION来分析吧。
当然不是每个控件都有CAPTION属性的,我们这里就用TButton类的Caption属性进行分析。
    打开每天我们都会使用的DELPHI,在FORM窗体上放一个按钮,得到一个Button1的按钮控件,按F12打天源程序,有没有找到这
    段代码呢:
Button1: TButton;
对了,在TButton上点击鼠标右键,在弹出的上下文菜单中选择第一项Find Declaration,找到TButton类的定义,如下所示:
  TButton = class(TButtonControl)
  private
    FDefault: Boolean;
    FCancel: Boolean;
    FActive: Boolean;
    FModalResult: TModalResult;
    procedure SetDefault(Value: Boolean);
。。。。。。

    原来TButton继承于TButtonControl类,呵呵:)
在左边的对象窗口(Exploring Unit.pas窗口)中找到TButton的CAPTION属性,如下图:
 
    双击CAPTION属性,找到定义CAPTION属性的源码,大家可能发现什么都没有,只有一个   
    property Caption;
    呵呵,写过组件的朋友都知道,按理Caption属性应该有读/写文本的方法啊?在哪里去了呢,呵呵,这里没有出现,当然应该
    在它的父类里了(这里只是申明Caption出来的地方),我们顺着刚才的方法继续在TButtonControl,发现也没有,最终我们
    在TControl类里找到了这个CAPTION,至于为什么是protected成员,我就不多说了:
  protected
    procedure ActionChange(Sender: TObject; CheckDefaults: Boolean); dynamic;
    procedure AdjustSize; dynamic;
    procedure AssignTo(Dest: TPersistent); override;
    procedure BeginAutoDrag; dynamic;
    function CanResize(var NewWidth, NewHeight: Integer): Boolean; virtual;
    function CanAutoSize(var NewWidth, NewHeight: Integer): Boolean; virtual;
    procedure Changed;
    procedure ChangeScale(M, D: Integer); dynamic;
。。。。。。
    property Caption: TCaption read GetText write SetText stored IsCaptionStored;
    看看GetText、SetText就是操作文本属性的函数了,我们找到GetText、SetText定义如下:
function GetText: TCaption;
procedure SetText(const Value: TCaption);
还有TCaption,它的定义居然是一个自定义类型:
TCaption = type string;
    说明GetText返回值和SetText的调用参数本来也就是一个string型的:)

    下面我们来看看GetText源码:
function TControl.GetText: TCaption;
var
  Len: Integer;
begin
  Len := GetTextLen;//得到文本长度
  SetString(Result, PChar(nil), Len);// 设置Result返回以Len指定的长度
  if Len <> 0 then GetTextBuf(Pointer(Result), Len + 1);//长度不为空,Result得到文本数据
end;

    如果不明白GetTextBuf的用法,看看如下的代码:
procedure TForm1.Button1Click(Sender: TObject);
var
  Buffer: PChar;
  Size: Byte;
begin
  Size := Edit1.GetTextLen;   file://得到EDIT1的文本长
  Inc(Size);               
  GetMem(Buffer, Size);          file://创建EDIT1文本长度大小的缓存空间
  Edit1.GetTextBuf(Buffer,Size);  file://由缓存得到文本,Buffer里的值就是Edit1.Text
  Edit2.Text := StrPas(Buffer);   file://Buffer转换为PASCAL字符类型数据
  FreeMem(Buffer, Size);      file://释放内存
end;
以上程序的行为同以下程序相当:
procedure TForm1.Button1Click(Sender: TObject);
begin
  Edit2.Text := Edit1.Text;
end;

    回到GetText函数,其中GetTextLen的作用是得到文本长度,GetTextBuf得到文本数据。
    SetText就更简单了,定义如下:
procedure TControl.SetText(const Value: TCaption);
begin
  if GetText <> Value then SetTextBuf(PChar(Value));
end;
    意思是如果设定的Value与原来的不同,则重新设置缓存文本。


    为了更深入VCL底部,我们再看看GetTextLen如何实现的(其实SetTextBuf和GetTextLen的实现过程相似):
function TControl.GetTextLen: Integer;
begin
  Result := Perform(WM_GETTEXTLENGTH, 0, 0);//WM_派发的是WINDOWS标准消息
end;
    看到这里想必大家都明白了,如果还不明白(没用过Perform),我看再看看Perform,它到底做了什么:
function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;
var
  Message: TMessage;
Begin
{你的消息赋予TMessage }
  Message.Msg := Msg; ;
  Message.WParam := WParam;
  Message.LParam := LParam;
Message.Result := 0;//0表示返回不处理
  if Self <> nil then WindowProc(Message);//不为空,将消息交给TControl的窗口过程WindowProc处理
  Result := Message.Result;//返回结果
end;
    这里主要再看看WindowProc做了什么,TControl里面WindowProc是这样定义的:
property WindowProc: TWndMethod read FWindowProc write FWindowProc;
在TControl的Create函数中:
constructor TControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FWindowProc := WndProc;
。。。。。。
    可见我们还要找到TControl 的WndProc过程才能明白究竟,
WndProc过程定义如下:
procedure WndProc(var Message: TMessage); override;
    实现:
procedure TControl.WndProc(var Message: TMessage);
var
  Form: TCustomForm;
  KeyState: TKeyboardState; 
  WheelMsg: TCMMouseWheel;
begin
  if (csDesigning in ComponentState) then
  begin
    Form := GetParentForm(Self);
    if (Form <> nil) and (Form.Designer <> nil) and
      Form.Designer.IsDesignMsg(Self, Message) then Exit
  end;
  if (Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST) then
  begin
    Form := GetParentForm(Self);
    if (Form <> nil) and Form.WantChildKey(Self, Message) then Exit;
  end
  else if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then
  begin
    if not (csDoubleClicks in ControlStyle) then
      case Message.Msg of
        WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK:
          Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
      end;
    case Message.Msg of
      WM_MOUSEMOVE: Application.HintMouseMessage(Self, Message);
      WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
        begin
          if FDragMode = dmAutomatic then
          begin
            BeginAutoDrag;
            Exit;
          end;
          Include(FControlState, csLButtonDown);
        end;
      WM_LBUTTONUP:
        Exclude(FControlState, csLButtonDown);
    else
      with Mouse do
        if WheelPresent and (RegWheelMessage <> 0) and
          (Message.Msg = RegWheelMessage) then
        begin
          GetKeyboardState(KeyState);
          with WheelMsg do
          begin
            Msg := Message.Msg;
            ShiftState := KeyboardStateToShiftState(KeyState);
            WheelDelta := Message.WParam;
            Pos := TSmallPoint(Message.LParam);
          end;
          MouseWheelHandler(TMessage(WheelMsg));
          Exit;
        end;
    end;
  end
  else if Message.Msg = CM_VISIBLECHANGED then
    with Message do
      SendDockNotification(Msg, WParam, LParam);
  Dispatch(Message);//派发消息
end;
    这里主要讲讲Dispatch方法,它根据传入的消息调用消息的句柄方法,如果在组件类和它的父类都没有找到消息的处理
句柄,Dispatch方法便会调用Defaulthandler(默认的消息处理方法),如下:
procedure TObject.Dispatch(var Message);
asm
    PUSH    ESI
    MOV     SI,[EDX]
    OR      SI,SI
    JE      @@default
    CMP     SI,0C000H
    JAE     @@default
    PUSH    EAX
    MOV     EAX,[EAX]
    CALL    GetDynaMethod
    POP     EAX
    JE      @@default
    MOV     ECX,ESI
    POP     ESI
    JMP     ECX

@@default:
    POP     ESI
    MOV     ECX,[EAX]
    JMP     DWORD PTR [ECX] + VMTOFFSET TObject.DefaultHandler//调用默认的消息处理方法
end;
    而默认的消息处理如下,在SYSTEM.PAS单元里:
procedure TObject.DefaultHandler(var Message);
begin
end;
    由以上代码看好像是没有任何处理过程,其实如果再调试代码,从CPU调试窗口发现有汇编代码执行,可见默认的消息处理是
    由编译器内置处理的。
从最表面的Button.caption,我们走到了编译器层,可见所有东西都能找到它固有的原点!以caption的分析为基础,我们可以继
续分析name属性和其它一些方法/函数。
希望我这篇‘散文’能给大家理出点头绪:)
 
  ehom(?!) ( ) 信誉:170  2003-11-12 9:50:57  得分:0
 
还没仔细看,但最后一部分不确切

JMP     dword ptr [ECX].vmtDefaultHandler

这句不会一开始就跳到基类TObject的DefaultHandler执行,而是跳到最后一级重载过DefaultHandler的派生类的DefaultHandler成
员函数执行,TObject.DefaultHandler一般是不会被执行的,因为它的下一级派生类中不会加一句多余的
inherited DefaultHandler(Message);,"可见默认的消息处理是由编译器内置处理的"是不确切的.

  ehom(?!) ( ) 信誉:170  2003-11-12 10:42:02  得分:0  
除了少量非常底层的内建函数是看不到源代码(如Swap,Abs等,它们是作为inline函数直接编译为指令,不提供代码),其它地方的源代
码基本都是可见的,编译器的工作就是编译代码,不会去做其它事的

TObject的成员DefaultHandler是虚拟方法,必须在子类中重载才能实现对消息的缺省处理,这和其它成员的处理是一样的,编译器不
会对其做特殊处理
 
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 10:48:52  得分:0   
我已经说过,如果在组件类和它的父类都没有找到消息的处理句柄,Dispatch方法最终便会调用Defaulthandler(默认的消息处理方法)

 
  ehom(?!) ( ) 信誉:170  2003-11-12 10:53:02  得分:0 
如上所言:
"JMP dword ptr [ECX].vmtDefaultHandler 这句不会一开始就跳到基类TObject的DefaultHandler执行,而是跳到最后一级重载过
DefaultHandler的派生类的DefaultHandler成员函数执行"
"默认的消息处理方法"是通过子类中重载实现
 
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 10:56:24  得分:0  
如果Dispatch派发的消息,已经不能找到处理消息的方法了,那调用默认的消息处理方法后这个消息由谁来处理呢?
 
  ehom(?!) ( ) 信誉:170  2003-11-12 11:01:49  得分:0  
当前类和TObject之间,最后一个重载过DefaultHandler的类是谁?就跳到该类的DefaultHandler执
 
  FrameSniper(§绕瀑游龙§) ( ) 信誉:175  2003-11-12 11:04:08  得分:0 
由当前类所在继承链中最近一层父类(也就是当前类的直接父类)来处理,如果处理不了,就继续向上查找,如果最后都找不到,
就调用TObject的DefaultHandler来进行处理....
 
  lxpbuaa(桂枝香在故国晚秋) ( ) 信誉:209  2003-11-12 11:07:29  得分:0  
ehom(?!) 说的是对的,TObject提供DefaultHandler的目的是子类覆盖(不是重载),从而实现消息的默认处理。
  
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 11:17:19  得分:0  
ehom(?!) 说的意思我明白,调用
JMP     DWORD PTR [ECX] + VMTOFFSET TObject.DefaultHandler//调用默认的消息处理方法
这句后,会强制转到最后一个重载过DefaultHandler的类,比如以下程序的执行过程
{Size := Edit1.GetTextLen;   file://得到EDIT1的文本长}
当执行TObject.DefaultHandler的时候,它会转到以下语句继续执行,但我觉得它是DELPHI编译器特殊处理的结果
procedure TCustomEdit.DefaultHandler(var Message);
begin
  case TMessage(Message).Msg of
    WM_SETFOCUS:
      if (Win32Platform = VER_PLATFORM_WIN32_WINDOWS) and
        not IsWindow(TWMSetFocus(Message).FocusedWnd) then
        TWMSetFocus(Message).FocusedWnd := 0;
  end;
  inherited;——》再到它的父类执行DefaultHandler函数。
    
  ehom(?!) ( ) 信誉:170  2003-11-12 11:53:15  得分:0  
说错了,是覆盖,最近算法写多了,最基本的词都用错了
cg1120:
还是不对,不会执行TObject中的虚拟方法,直接执行子类中覆盖的成员
 
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 12:08:51  得分:0  
to ehom(?!)
如果子类没有覆盖的消息呢?你认为程序会如何处理?!
 
  ehom(?!) ( ) 信誉:170  2003-11-12 12:23:58  得分:0  
自然是调用TObject.DefaultHandler,不作任何处理
 
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 12:31:02  得分:0 
你认为调用TObject.DefaultHandler不会作任何处理可否有依据
 
  reallike(认真学习Cpp用Cpp考虑delphi) ( ) 信誉:102  2003-11-12 12:33:28  得分:0  
据我所知,当消息处理里面调用inherited的时候,消息会被送进DefaultHandler

  reallike(认真学习Cpp用Cpp考虑delphi) ( ) 信誉:102  2003-11-12 12:36:45  得分:0
 
而且是一级一级的调用,在入栈里面会看到压入了四个DefaultHandler。

其实分别是哪一级的我也没有深究。
 
  myling(阿德) ( ) 信誉:100  2003-11-12 13:05:37  得分:0 
我也同意ehom(?!)

如果如果子类没有覆盖的消息,调用TObject.DefaultHandler,不作任何处理

说到依据……
只能是
procedure TObject.DefaultHandler(var Message);
begin
end;

没有做任何处理了

  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 13:55:09  得分:0 
to  myling(阿德)
我并不认为begin end;之间没有任何代码就代表编译器没有执行任何的工作,Delphi的编译器如果执行到TObject.Destroy语句,
会调用TObject.BeforeDestruction和ClassDestroy,TObject.Destroy在system单元里的构造过程如下:
destructor TObject.Destroy;
begin
end
  firetoucher(风焱) ( ) 信誉:230  2003-11-12 14:27:54  得分:0
 
 
 
好长。。。。。。
sorry,没有功夫看。。。。。。
说点其他的感受:
我对Delphi将Dispath和Defaulthandler放入TObject感到有点疑惑。why?
在VC的MFC架构中的老祖宗CClass没有这些,只是在其子类中才有,而Delphi却放到的老祖先哪里!
这对其他的类比如很多用户自定义类型或者非windows平台即没有用又缺乏平台可移植性。
 
  ehom(?!) ( ) 信誉:170  2003-11-12 14:31:05  得分:0
 
但区别在于你这说的是析构函数

  FrameSniper(§绕瀑游龙§) ( ) 信誉:175  2003-11-12 14:35:15  得分:0
 
begin和end本身就是一个过程体,只不过写法特殊而已,在SEH中begin和end中都有相应的代码被执行,同时对于接口的计数引用
在end中也有释放引用计数的代码被执行!
至于楼主的疑问,我看不是什么疑问,Delphi对消息系统的封装就是按级进行处理的!

 
  ehom(?!) ( ) 信誉:170  2003-11-12 14:35:18  得分:0
 
构造函数和析构函数不具普遍性,他们在执行前会先执行一些默认的代码

 
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 14:39:13  得分:0
 
to firetoucher(风焱)
自定义的消息你是会加上自定义的消息处理过程的。
to ehom(?!)
我想你都是按正常、VCL能处理得了的进行跟踪,你有没有跟踪过VCL类库最终没有处理的消息进行跟踪?
 
  plainsong(短歌) ( ) 信誉:103  2003-11-12 14:42:46  得分:0

事实上VCL中实现消息分派的方法就是以前在BC3中出现但在BC4中消失的DDVT技术,它允许我们在编写类时为一个虚函数
(现在是改用动态函数)指针一个整数标识,编译时会自动为带有标识的函数生成一张查找表。
在BC3中声明这个的函数的语法是:
virtual void Foo(...) = [XXX];
而现在在Object Pascal中则是:
procedue Foo(...); message XXX;

而在Dispatch中的代码就是取出参数的前四字节,把它看成一个整数,用这个整数作为标识在这张表中搜索,如果搜索不到的话
再调用DefaultHandle。而DefaultHandle则是一个虚函数,可以被override。在窗口类代码中,它被override并去调用
DefWindowProc(或DefMDIChildPoc、或是被超类化的预定义控制的本来的消息函数等)。

我觉得Dispatch本来不需要成为virtual的,之以要让它成为overridable可能是为了在BCB中使用。因为BCB中不再支持DDVT技术,
在BCB中写消息函数时是用一组宏封装了对Dispatch的override和一个switch分支。

当消息处理里面调用inherited的时候,并不会直接去调用DefaultHandler,而是会去调用最近基类中标识相同的函数
(注意代码是在具有标识的动态函数中),如果追溯到TObject都没有找到,才会去调用DefaultHandler。大家可以试一下面的代码:

type
  TBase = class
  private
    procedure X(var A); message 11;
  end;

  TDerived = class (TBase)
  private
    procedure Y(var B); message 11;
  end;
{ TBase }

procedure TBase.X(var A);
begin
  ShowMessage('TBase.X');
end;

{ TDerived }

procedure TDerived.Y(var B);
begin
  ShowMessage('TDerived.Y');
  inherited;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Obj: TDerived;
  M: Integer;
begin
  Obj := TDerived.Create;
  M := 11;
  Obj.Dispatch(M);
  Obj.Free;
end;

此外,我觉得cg1120用来反驳myling的例子并合理,因为Destroy是个destructor面DefaultHandler只是个pocedure,
我们知道Delphi面对constructor和destuctor的调用都是在语言级进行特殊处理的,而普通成员则没有这一步。

  ehom(?!) ( ) 信誉:170  2003-11-12 15:48:04  得分:0
 
要用Delphi的调试器跟踪消息的处理是不太可能的,因为消息的处理函数是由操作系统回调的

"正常、VCL能处理得了"又是什么概念?指不存在对应的消息号的处理方法?你上面自己不也说了吗?DMT中未找到相应的动态方法,
自然就是跳转到VMT中[ECX].vmtDefaultHandler指向的地址,这点还有疑问?

只有通过inherited DefaultHandler(Message)才能继承调用上一层的DefaultHandler.而不是一层层的自动调用
 
  ehom(?!) ( ) 信誉:170  2003-11-12 15:52:41  得分:0
 
TTest = class(TObject)
  procedure DefaultHandler(var Message);
end;

procedure TTest.DefaultHandler(var Message);
begin
  inherited DefaultHandler(Message);
end;

var
  Test:TTest;
  I: Integer;
begin
  Test := TTest.Create;
  Test.DefaultHandler(I);
  Test.Free;
end;
设个断点试试吧,其实编译器只是忠实的编译了上面的代码,不存在"内置处理"

  ehom(?!) ( ) 信誉:170  2003-11-12 15:54:22  得分:0  
少了个override
 
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 16:06:31  得分:0
 
DELPHI不能跟踪不代表消息处理函数不能跟踪
我的观点还是TObject.DefaultHandler绝对在编译器级有处理
procedure TObject.DefaultHandler(var Message);
begin
end
  forgot2000(忘记2000年) ( ) 信誉:100  2003-11-12 16:27:29  得分:0
 
我觉得Delphi编译器内置了使用TObject的DefaultHandler方法来进行消息的默认处理,我们可以一直回溯到TObject基类的
汇编级代码,在其指向VMT(虚方法表)指针所指向的虚方法地址中,在偏移地址为-16处指向的地址,就是默认处理消息的虚方
法地址,在
procedure TObject.DefaultHandler(var Message);
begin
end;
中我们来到最后调试的汇编代码地址就是走到该处,至于做了些什么事,你问问CPU才知道!
 
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 16:29:49  得分:0
 
但是即使Test.DefaultHandler(I);执行了后,还是回到了procedure TObject.DefaultHandler(var Message)的END,如果没有
最后的处理工作,还执行回去有何意义
 
  forgot2000(忘记2000年) ( ) 信誉:100  2003-11-12 16:32:54  得分:0
 
来自Delphi 6的帮助,摘录如下:

A class-type value is stored as a 32-bit pointer to an instance of the class, which is called an object. The internal
 data format of an object resembles that of a record. The object抯 fields are stored in order of declaration as a
 sequence of contiguous variables. Fields are always aligned, corresponding to an unpacked record type. Any fields
 inherited from an ancestor class are stored before the new fields defined in the descendant class.
The first 4-byte field of every object is a pointer to the virtual method table (VMT) of the class. There is exactly
one VMT per class (not one per object); distinct class types, no matter how similar, never share a VMT. VMTs are built
automatically by the compiler, and are never directly manipulated by a program. Pointers to VMTs, which are
 automatically stored by constructor methods in the objects they create, are also never directly manipulated by a
 program.

The layout of a VMT is shown in the following table. At positive offsets, a VMT consists of a list of 32-bit method
 pointers梠ne per user-defined virtual method in the class type梚n order of declaration. Each slot contains the
 address of the corresponding virtual method抯 entry point. This layout is compatible with a C++ v-table and with
 COM. At negative offsets, a VMT contains a number of fields that are internal to Object Pascal抯 implementation.
 Applications should use the methods defined in TObject to query this information, since the layout is likely to
 change in future implementations of Object Pascal.

Offset Type Description
-76 Pointer pointer to virtual method table (or nil)
-72 Pointer pointer to interface table (or nil)
-68 Pointer pointer to Automation information table (or nil)
-64 Pointer pointer to instance initialization table (or nil)
-60 Pointer pointer to type information table (or nil)
-56 Pointer pointer to field definition table (or nil)
-52 Pointer pointer to method definition table (or nil)
-48 Pointer pointer to dynamic method table (or nil)
-44 Pointer pointer to short string containing class name
-40 Cardinal instance size in bytes
-36 Pointer pointer to a pointer to ancestor class (or nil)
-32 Pointer pointer to entry point of SafecallException method (or nil)
-28 Pointer entry point of AfterConstruction method
-24 Pointer entry point of BeforeDestruction method
-20 Pointer entry point of Dispatch method
-16 Pointer entry point of DefaultHandler method
-12 Pointer entry point of NewInstance method
-8 Pointer entry point of FreeInstance method
-4 Pointer entry point of Destroy destructor
0 Pointer entry point of first user-defined virtual method
4 Pointer entry point of second user-defined virtual method

  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 16:34:07  得分:0 
 
TTest = class(TObject)
//  procedure DefaultHandler(var Message);
end;

procedure TTest.DefaultHandler(var Message);
begin
  file://inherited DefaultHandler(Message);
end;
如果都注释掉,同样会回到procedure TObject.DefaultHandler(var Message)
 
  forgot2000(忘记2000年) ( ) 信誉:100  2003-11-12 16:36:41  得分:0
  
请注意看
-16 Pointer entry point of DefaultHandler method
那一段,并不是
-16 Pointer entry point of DefaultHandler method (or nil)

这就说明,DefaultHandler肯定应该有入口地址,执行编译器对默认消息的内置处理。
 
  FrameSniper(§绕瀑游龙§) ( ) 信誉:175  2003-11-12 16:36:45  得分:0
 
这个破CSDN,害的我还要重写!!!!

这个问题怎么又跑到内置处理了,我不知道各位说的内置处理的衡量标准是什么,是不是就是CPU窗口中对应OP代码行分解出的
汇编码中有对System等内核单元的方法或过程的调用?但我想应该是有一些是存在这样的调用的,最明显的就是一个接口类型的
变量在作用域对应过程的end中存在对计数的减运算,同样对于字串也有内置操作....

至于消息机制的过程没有必要再讨论什么了,无非就是如下三步:
1.使用Perform进行发送;
2.在窗口默认WndProc过程中进行处理,如果没有对应处理器就使用Dispatch进行分发,否则直接处理;
3.使用Dispatch进行分发的目标是直接父类的DefaultHandler方法,并且在里面寻找相应处理过程,如果有则直接处理,无则继续
到当前类的直接父类中进行处理查找...,最后一直到TObject.DefaultHandler(至于这个方法是否有内置处理不清楚,没有看过
CPU窗口)。

而且,另外一点个人认为既然是一个良好的类库,不应该出现对于系统消息在消息处理链中捕捉不到的情形....
 
  ehom(?!) ( ) 信誉:170  2003-11-12 16:46:54  得分:0
 
你都注释掉了procedure DefaultHandler(var Message); override;,不就是表示不覆盖该方法?既然不覆盖,那通过VMT中的
vmtDefaultHandler自然就是跳转到TObject.DefaultHandler

你只把inherited DefaultHandler(Message);注释掉试试

forgot2000的基本观点就是最开始说明的你的错误所在,难道对这第一个错误都还存在异议?

这里绝对是跳到最后一级覆盖过DefaultHandler的派生类的成员函数执行
 
  johncsdn(雷子) ( ) 信誉:93  2003-11-12 16:52:01  得分:0 
暈!
我想是不是大家腦子裡最基本的觀念是一致的,隻是表達的方法不對???
 
  reallike(认真学习Cpp用Cpp考虑delphi) ( ) 信誉:102  2003-11-12 16:52:29  得分:0 
 
…… 大家看Call stack最能说明一切了。

再说了,已经跑题了。集中在DefaultHandler这个垃圾桶里面了……
 
  ehom(?!) ( ) 信誉:170  2003-11-12 16:52:53  得分:0
 
至于forgot说的第二点就是为什么TObject要实现一个空函数TObject.DefaultHandler的原因所在,如果在CPU窗口看只存在一句
ret指令

  forgot(忘记forgot2000) ( ) 信誉:100  2003-11-12 16:53:05  得分:0
 
虽然对于系统底层原理所涉及的细节我们没必要彻底深究,但基本的概念还是应该搞清楚。希望Delphi版块能多一些类似的专题讨论,
对于大家都能有一个促进和提高。

  FrameSniper(§绕瀑游龙§) ( ) 信誉:175  2003-11-12 16:54:09  得分:0
 
好了,我看过了,TObject.DefaultHandler没有任何内置处理,对应汇编代码如下:

mov edx,esp
mov eax,ebx
mov ecx,[eax]
call dword prt [ecx-$10]

而且,我感觉楼主担心所谓的有的消息最后得不到处理好象没有多大意义,还是我原来的那句话,一个设计良好的类库在对Windows
消息系统进行封装的时候不太可能把一个系统消息漏掉!

 
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 16:54:30  得分:0
 
to ehom(?!)
不明白你的意思,不管你覆不覆盖DefaultHandler,TTest都会执行
 
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 16:56:12  得分:0
 
TTest都会执行到TObject.DefaultHandler
 
  FrameSniper(§绕瀑游龙§) ( ) 信誉:175  2003-11-12 16:59:11  得分:0
 
至于是否是跳转到直接父类进行消息处理器的查找这个问题我看没有必要再争论了,事实如此....

现在问题好象已经被转到是否在TObject.DefaultHandler里面有所谓的内置处理,我看了下CPU窗口----没有这种处理!而且我感觉
CG1120老兄一直提的如果最后在整个继承链上都找不到消息处理器这个问题没有必要担心----原因很简单,一个使用了20年的类库
如果连这种基本的系统消息都忽略漏掉,那不是太有点.....

  ehom(?!) ( ) 信誉:170  2003-11-12 17:02:50  得分:0
 
这个你可以自己再试试,可能是你没有看清楚我一开始就说明的话

TTest = class(TObject)
  procedure DefaultHandler(var Message);
end;

procedure TTest.DefaultHandler(var Message);
begin
  file://inherited DefaultHandler(Message);
  file://把上面这句注释掉
end;

var
  Test:TTest;
  I: Integer;
begin
  Test := TTest.Create;
  Test.DefaultHandler(I);
  Test.free;
end;

inherited DefaultHandler(Message);//少了这句,就不可能会去继承祖先类的方法的执行

  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 17:03:41  得分:0 
 
to FrameSniper(§绕瀑游龙§)
在TObject.DefaultHandler里并没有执行begin,但执行了end,即结束消息处理!

  johncsdn(雷子) ( ) 信誉:93  2003-11-12 17:07:36  得分:0
 
爭論的焦點:
JMP     DWORD PTR [ECX] + VMTOFFSET TObject.DefaultHandler//调用默认的消息处理方法
end;
    而默认的消息处理如下,在SYSTEM.PAS单元里:
procedure TObject.DefaultHandler(var Message);
begin
end;
    由以上代码看好像是没有任何处理过程,其实如果再调试代码,从CPU调试窗口发现有汇编代码执行,可见默认的消息处理是
    由编译器内置处理的。
file://*****************
我想這
/*-16 Pointer entry point of DefaultHandler method*/
是不是表示系統會對這個消息做些處理,隻是處理的工作量的問題而已(或者處理也隻是什麼也不做)。
 
  plainsong(短歌) ( ) 信誉:103  2003-11-12 17:12:13  得分:0
 
“这对其他的类比如很多用户自定义类型或者非windows平台即没有用又缺乏平台可移植性。”

我以为这样说并不正确。DDVT并不只是提供给响应Windows消息用的,你完全可以在其它场合使用它。事实上,可以说它是介于
reflection和vtpr之间的一种多态机制,速度比reflection快,使用起比vtpr灵活。在你需要使用一个按整数分派的机制的时候,
你完全可以使用它,而不需要自己写一个int-Event的映射。

正因为一机制并不是为Windows消息响应量身定作的,所在DefaultHandler“什么都不做”也是自然而然的了。事实上,在我们
写Windows时,很明显一点就是要把不处理的消息传给DefWindowProc(如果是自己的窗口类而不是从预定义控制超类而来的话),
而这一步的完成恰恰是封装了系统的Window对象的VCL类TWinControl的DefaultHandler中完成的。而TObject把它定义为虚的正是
为了我们可以写自己的未处理的消息的缺省处理,而不写成抽象的则是为了当缺省动作就是忽略时我们不需要去override它。

  ehom(?!) ( ) 信誉:170  2003-11-12 17:14:01  得分:0
 
你又错了,只要函数中没有定义并使用变量,begin都不会执行,最明显的证据上面不能设断点

在CPU窗口看看,此时的begin语句是有意义的,它代表下面的N个(N>=1)PUSH压栈指令
 
  ehom(?!) ( ) 信誉:170  2003-11-12 17:17:11  得分:0
 
Borland在BC++中放弃这种消息封装机制,应该主要是因为和C++标准不兼容

  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 17:18:17  得分:0
 
那请问为什么又回到了TObject.DefaultHandler的end处?!

  ehom(?!) ( ) 信誉:170  2003-11-12 17:24:09  得分:0
 
我在倒数第三句已经回答

  ehom(?!) ( ) 信誉:170  2003-11-12 17:26:31  得分:0

可能是你对override和inherited的理解出了点小问题

 
 
 
 
  FrameSniper(§绕瀑游龙§) ( ) 信誉:175  2003-11-12 17:28:12  得分:0
 
To CG1120

  我没有看到end里面有什么附加操作啊?


  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 17:32:04  得分:0
 
那你觉得override和inherited该如何理解呢,你想争论的问题是什么呢?

  ehom(?!) ( ) 信誉:170  2003-11-12 17:38:15  得分:0

我上面已经说明了,没看到吗?

inherited DefaultHandler(Message);//少了这句,就不可能会去继承祖先类的方法的执行

也就是所谓的TObject.DefaultHandler不会被调用

  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 17:41:31  得分:0

到了TObject.DefaultHandler什么都不做也是一种处理

  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 17:44:21  得分:0
 
to ehom(?!) ,我说的注释掉不要,指的是:
TTest = class(TObject)
end;

var
  Test:TTest;
  I: Integer;
begin
  Test := TTest.Create;
  Test.DefaultHandler(I);
  Test.Free;
end;

  FrameSniper(§绕瀑游龙§) ( ) 信誉:175  2003-11-12 17:48:11  得分:0
 
呵呵,CG1120兄,偶真不知道这个问题讨论这么长时间你哪里认为还有不对头的地方!

在TObject.DefaultHandler里面没有任何内置调用,在begin和end中间也没有任何内置调用,你所说的end里面有其他内置调用我在
CPU窗口中没有看到....

既然如此,TObject.DefaultHandler仅仅是一个空架子,如果不被覆盖,是没有意义的....

既然被覆盖才可以有意义,那么覆盖的结果就应该是尽量去对系统消息的处理做到完整,而且这种尽量我想VCL和RTL已经做的很完善了
,没有必要再来那些无谓的假设了....你说呢?

  lxpbuaa(桂枝香在故国晚秋) ( ) 信誉:209  2003-11-12 17:49:33  得分:0
 
问题集中在TObject.DefaultHandler是否作了隐含处理上。我个人倾向于未作隐含处理的观点。

    查看VCL的主要类,自TObject以下,TControl第一次对DefaultHandler作了覆盖,处理了三个消息:
    WM_GETTEXT、WM_GETTEXTLENGTH和WM_SETTEXT。在这里也没有调用祖先类的DefaultHandler(即没有inherited DefaultHandler)。
    接下来,TWinControl再次作了覆盖。其处理主要流程是:

if FHandle <> 0 then  file://窗口已经建立
begin
  if …… 处理一些类型的消息
  else
     Result := CallWindowProc(FDefWndProc, FHandle, Msg, WParam, LParam); 
end else
  inherited DefaultHandler(Message); file://调用TControl的处理

    我个人认为,CallWindowProc处才是实现真正的缺省处理,其中:
  FDefWndProc := WindowClass.lpfnWndProc;
  WindowClass.lpfnWndProc := @DefWindowProc; file://DefWindowProc是一个API函数。当我们用CreateWindow和CreateWindowEx创建窗口
  时(一个TWinControl实例就是通过调用CreateWindowEx来建立窗口的),在窗口过程中通常都要直接或者间接调用DefWindowProc实现
  消息默认处理。
  
    另要说明的是,TControl.WndProc最后调用虚方法Dispatch来派遣消息,Dispatch中再:
    JMP     dword ptr [ECX].vmtDefaultHandler。因为Dispatch是一个虚方法,所以JMP是定位到本类(如一个TButton或者TForm本身
    而不是其祖先TObject)的VMT中DefaultHandler的位置上。
    所以,我认为TObject.DefaultHandler只是以虚方法的方式给子类留一个入口、只是一种手段,而本身并没有隐含的默认处理。

  ehom(?!) ( ) 信誉:170  2003-11-12 17:59:02  得分:0

我知道你的意思,像你那样什么都不做,其派生类都没有覆盖过该成员,自然是调用TObject.DefaultHandler,这是面向对象的常识,还有
什么讨论的意义?

我反驳的是你这句:"不明白你的意思,不管你覆不覆盖DefaultHandler,TTest都会执行TObject.DefaultHandler"

覆盖了,就不会再主动调用其祖先类的方法.这就是核心的理解错误之一所在.

  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 18:01:09  得分:0
 
lxpbuaa(桂枝香在故国晚秋)说的是对的,但我的关点是收到消息,没有处理也算一种处理

  plainsong(短歌) ( ) 信誉:103  2003-11-12 18:20:02  得分:0
 
看来其实大家说都是一回事,都是说TObject.DefaultHandler“什么都没作”,只是cg1120认为“什么都不作也是一种处理”,这种
名词概念上的争执实在是没有什么意义了。

不过lxpbuaa说:“因为Dispatch是一个虚方法,所以JMP是定位到本类……”这种说法有点问题,即使Dispatch不是虚方法,也一样
能定位到本类,因为在JMP之前,ECX中已经存放了对象的VTPR,注意CALL    __GetDynaMethod和MOV ECX,EAX两句。我仍然觉得
Dispatch成为虚方法的最大意义还是在BCB中可以用overrideDispatch的方法来实现消息映射。
还用我上面的例子:
type
  TBase = class
  private
    procedure X(var A); message 11;
  public
    procedure Dispatch2(var Message);
  end;

  TDerived = class (TBase)
  private
    procedure Y(var B); message 11;
  end;
{ TBase }

procedure __GetDynaMethod;
{       function        GetDynaMethod(vmt: TClass; selector: Smallint) : Pointer;       }
asm
        { ->    EAX     vmt of class            }
        {       SI      dynamic method index    }
        { <-    ESI pointer to routine  }
        {       ZF = 0 if found         }
        {       trashes: EAX, ECX               }

        PUSH    EDI
        XCHG    EAX,ESI
        JMP     @@haveVMT
@@outerLoop:
        MOV     ESI,[ESI]
@@haveVMT:
        MOV     EDI,[ESI].vmtDynamicTable
        TEST    EDI,EDI
        JE      @@parent
        MOVZX   ECX,word ptr [EDI]
        PUSH    ECX
        ADD     EDI,2
        REPNE   SCASW
        JE      @@found
        POP     ECX
@@parent:
        MOV     ESI,[ESI].vmtParent
        TEST    ESI,ESI
        JNE     @@outerLoop
        JMP     @@exit

@@found:
        POP     EAX
        ADD     EAX,EAX
        SUB     EAX,ECX         { this will always clear the Z-flag ! }
        MOV     ESI,[EDI+EAX*2-4]

@@exit:
        POP     EDI
end;

procedure TBase.Dispatch2(var Message);
asm
    PUSH    ESI
    MOV     SI,[EDX]
    OR      SI,SI
    JE      @@default
    CMP     SI,0C000H
    JAE     @@default
    PUSH    EAX
    MOV     EAX,[EAX]
    CALL    __GetDynaMethod
    POP     EAX
    JE      @@default
    MOV     ECX,ESI
    POP     ESI
    JMP     ECX

@@default:
    POP     ESI
    MOV     ECX,[EAX]
    JMP     dword ptr [ECX].vmtDefaultHandler
end;

procedure TBase.X(var A);
begin
  ShowMessage('TBase.X');
end;

{ TDerived }

procedure TDerived.Y(var B);
begin
  ShowMessage('TDerived.Y');
  inherited;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Obj: TBase;
  M: Integer;
begin
  Obj := TDerived.Create;
  M := 11;
  Obj.Dispatch2(M);
  Obj.Free;
end;
大家试一下就明白了,即使调用的是TBase.Dispatch2,即使它不是一个虚方法,即使变量声明类型是TBase,它依然正确地了
TDerived.Y。

  lxpbuaa(桂枝香在故国晚秋) ( ) 信誉:209  2003-11-12 18:52:48  得分:0
 
其实所有的消息都可以得到处理,就是因为有:CallWindowProc(FDefWndProc, FHandle, Msg, WParam, LParam); 。

plainsong(短歌) :的确如你所说,与Dispatch是否为虚拟方法无关。

  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-12 20:02:23  得分:0

procedure TObject.DefaultHandler(var Message);
begin
end;
DefaultHandler过程执行的结果是一个简单的返回,没做其它处理,这个从CPU汇编窗口可以看出来

  hkbarton(宁静至远||淡泊明志) ( ) 信誉:105  2003-11-12 22:35:12  得分:0
 
好,到现在已经全部看完了,一贴不漏,其实大家的讨论结果已经是一致的了,那就是
TObject.DefaultHandler的确什么也没有做,我认为 lxpbuaa(桂枝香在故国晚秋) 提到的CallWindowProc已经比较清楚的
说明了问题
 
  billy_zh(牛仔) ( ) 信誉:100  2003-11-12 22:40:08  得分:0
 
建议大家多讨论一些比较实用的,
像MIDAS如何更新处理Delta包、
SocketConnection是如何连接的、
WebSnap响应请求的流程等。
不错,关注!
 
  ghao0(干什么) ( ) 信誉:98  2003-11-13 10:09:00  得分:0
爭論的焦點:高手能不能总结一下
爭論的焦點:
JMP     DWORD PTR [ECX] + VMTOFFSET TObject.DefaultHandler//调用默认的消息处理方法
end;
    而默认的消息处理如下,在SYSTEM.PAS单元里:
procedure TObject.DefaultHandler(var Message);
begin
end;
    由以上代码看好像是没有任何处理过程,其实如果再调试代码,(从CPU调试窗口发现有汇编代码执行,可见默认的消息处理是
    由编译器内置处理的。(是吗?))(一般是什麼也不做?)(对于可编译的源代码,一定符合语法,但不可编译的呢?...?)
file://*****************
我想這
/*-16 Pointer entry point of DefaultHandler method*/
(是不是表示系統會對這個消息做些處理,隻是處理的工作量的問題而已(或者處理也隻是什麼也不做)。)
 
  billy_zh(牛仔) ( ) 信誉:100  2003-11-13 10:17:00  得分:0
  to cg1120(代码最优化-§惟坚韧者始能遂其志§)
  midas实不实用并不重要,深入到VCL就是要了解其数据封包的处理过程,
  别告诉我用了COM+就不需要数据封包了。

  firetoucher(风焱) ( ) 信誉:230  2003-11-13 11:58:00  得分:0
 
 
  昨天就看到这个帖子人气很旺,可惜没有时间看,今天偶一起床就过来了(不好意思,和周公多聊了一会儿,所以......)
可惜大家"吵"的差不多,都偃旗息鼓了。
说点其他的吧
1 注意Dispatch中的语句CMP SI,0C000H
如果你自己定义的消息的值超过了0c00,系统会直接调用defaulthandler而不会在dynamic table中查找messasge handler

2 系统是如何查找消息handler的?
消息handler在Delphi中是使用dynamic的方法实现的,而其方法在dynamic table中的index就是消息的值也就是上面汇编的SI,通过
调用GetDynaMethod,在类的dynamic table中由子类到父类递归查找对应的index,调用完后在dipatch中通过判断ZF,转入相应
handler或者defaulthandler

3 用户也可以定义dynamic方法,在Delphi中用户用dynamic关键字定义的方法,其在dynamic table中的index(也就是传入
GetDynaMethod的寄存器SI的值)是从$FFFF开始的降序编码

4 消息和用户定义的dynamic都是通过GetDynaMethod,而且都通过dynamic table,只是通过其SI的大小进行的区分,也就是说过多的
使用定义dynamic也会降低系统的效率(但这样的机制和C++中用宏处理消息的机制效果对比如何,不知大家有无高见)。

5 关于defaulthandler的调用只是虚函数的调用
MOV     ECX,[EAX]
JMP     DWORD PTR [ECX] + VMTOFFSET TObject.DefaultHandler
如何调用,这两句已经非常清楚了,不用再说了吧:)

6 cg兄误会我上面的疑问了,我的意思是从框架的设计模式上说,我觉得没有必要将消息的处理接口(dispatch和defaulthandler)
在TObject中实现,为何不放到TWincontrol或者TPerestent或者TControl等中?比如建了一个 人员类或者一个全局的singleton类等,

没有太大需要消息处理。
 
  ihihonline(小小→杯酒释襟怀,一诺抛肝胆) ( ) 信誉:171  2003-11-13 12:11:00  得分:0

  感觉,心里都很明白,无论是ehom或是代码最优化都说的没有错,代码最优化也可能出现了一点笔误;
procedure TObject.DefaultHandler(var Message);
begin
end;
无论从这看还是CPU看,没有做什么;
其实,焦点完全可以集中在故国晚秋那儿;他说的也没错;就是:
CallWindowProc
很明显,这场争论可能就是一些笔误引起的,当然,是否是真的理解对错,我也不知道,不过我想应该现在不会出现这个阶段;
大家继续,偶吃饭去了;
 
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-13 12:51:00  得分:0
 
  firetoucher(风焱)关于dynamic的阐述很到位,但这些对于最新的delphi.net都已经是过时的技术了,最新的delphi8或所谓的
  delphi.net是通过MessageMethodAttribute(CLR提供的特性Attribute)来模拟现在的动态方法(dynamic),在定义的时候,编译器
  指定新的特性和编号,TObjectHelper.Dispatch通过新特性和编号,调用动态方法/响应消息函数。如果没有找到则肯定还是调用
  TObject.DefaultHandler方法处理的!
    TObject.DefaultHandler吸收了所有不能处理的消息和方法,我还是那个关点,即便不处理,还是一种处理,或者说
    TObject.DefaultHandler能够处理消息/方法的异常情况
 
  ehom(?!) ( ) 信誉:170  2003-11-13 15:00:00  得分:0
 
  "TObject.DefaultHandler吸收了所有不能处理的消息和方法"
这正是从头到尾要指出的错误所在!!!
不管你所说的处理的定义是什么,什么是处理,一开始就不是要和你争论这类问题!
这么明白点的说吧,所有所谓的控件都是从TControl派生的,这里已经覆盖了DefaultHandler,对控件接收到消息的默认处理过程中,
永远都到不了TObject.DefaultHandler,TObject.DefaultHandler只是出于VCL设计上的需要,它完全没有任何实际用处。
 
  plainsong(短歌) ( ) 信誉:103  2003-11-13 15:30:00  得分:0
 
  firetoucher说:“没有必要将消息的处理接口(dispatch和defaulthandler)在TObject中实现……”,然后ehom又
说:“TObject.DefaultHandler只是出于VCL设计上的需要,它完全没有任何实际用处。”我觉得很有问题。
  把Dispatch放到TObject中是有道理的。message handler功能是一种很有用的功能,它并不是天生提供给Windows Message
使用的,你完全可以在任何你需要根据整数进行分派的情况下使用这种机制。
  而Dispatch函数则是这一机制的一个重要组成部分,因为它是执行按整数分派的操作。很难想象Object Pascal允许你定义
message handler method但却有提供按message调用的能力……
  如果把Dispatch放到库中的某个类中,那么,当我们从TObject衍生一个类并需要这种机制时又该如何呢?大概只能
Ctrl-C&&Ctrl-V了;即使如此,这也不是解决办法,因为它把库的代码与语言机制紧密耦合在了一起,如果语言中对于
message handler的实现机制有所改变,你的代码就需要修改……
  因此说,Dispatch放到TObject中是有必要的,只有这样,才能从语言上保证message handler机制的完整性。记住:TObject
与其它类不同,它是语言的一部分,而TControl只是VCL类库中的一个类。
  而一旦Dispatch放到了TObject中,DefaultHandler也就没有问题了,当然也要在TObject或者更高层(好象没有更高层了)中
定义,唯一问题就是它应该是abstract还是virtual。这两种各有优缺点,不过Delphi好象并不鼓励使用abstract。
 
  FrameSniper(§绕瀑游龙§) ( ) 信誉:175  2003-11-13 15:34:00  得分:0
 
  这个帖子下午看了半天,CG1120的观点不言自明,从一开始就是错误的!EHOM的观点正确....同时故国的进一步分析直接进入
  TWinControl找到了系统对消息的默认处理来源于DefWindowProc这个封装API的函数,至此我看问题很明白了,一开始争论的焦点
  很明白,就是消息处理是否是直接跨到TObject.DefaultHandler,然后又转移到这个方法是否有内置处理,所有的争论CG1120的观
  点都是错误的....既然现在问题明白了,哪个同志给偶讲讲汇编啊?
 
  cg1120(代码最优化-§惟坚韧者始能遂其志§) ( ) 信誉:257  2003-11-13 15:48:00  得分:0
 
  “消息处理是否是直接跨到TObject.DefaultHandler”,肯定不是直接跨到TObject.DefaultHandler,我说过,只有处理不了后再
  会执行TObject.DefaultHandler,还有“是否有内置处理”,我想是处理了一个返回(即空处理),还有TObject.DefaultHandler
  吸收了所有不能处理的消息和动态方法也是我的关点,如果有很有说服力的证据,我会信服的,真理是摆在哪里的,不是说是就是,
  说不是就不是的。
 
  ehom(?!) ( ) 信誉:170  2003-11-13 16:29:00  得分:0
 
  呵呵,这证据不够明显吗?TControl中覆盖了DefaultHandler,而又没有用inherited去继承调用祖先类的方法,处理不了?我一直
  不理解这里的处理不了是什么意思?不过不用管,不管何种情况,都在这里返回了,没有任何机会调用TObject.DefaultHandler。
  如果一定要怀疑这里编译器做了什么特殊处理,设个断点,看看汇编代码应该是非常清楚的。
至于plainsong所言,我有点莫名其妙,这和我对TObject.DefaultHandler的结论有关系?我的这个结论不正是证实你的观点。

  2 plainsong(短歌)

1 message机制是有用,但并不应该是一个TObject基类应该具有的功能;
2 Dispatch虽然是virtual,但压根就没有被OWL重载过,而且TObject DefaultHandler正如ehom所说,不仅什么事情没有做,而且出
世不久就被TControl重载了;
3 类的封装应该是接口,而不是应该把“可能”用到的东西像添头一样塞进来,这可以参见设计模式中的stategy;如果像你所说,
和message一样有用的东西多了,比如Load Save...那所有的全部都加进来?!
4 我觉得message handler的功能应该在Tcontrol及其子类中实现,如果需要message功能,可以从TControl继承而不是TObject
(参见VC++的MFC架构)

2 FS
我的大概意思就是dynamic方法的调用,不知道你哪里不明白?

  plainsong(短歌) ( ) 信誉:103  2003-11-13 18:24:00  得分:0
 
  ehom:
  我并不反对你说的“TObject.DefaultHandler什么消息都没处理”的观点,我只是对“TObject.DefaultHandler只是出于VCL设
计上的需要,它完全没有任何实际用处。”这句话有不同意见。
firetoucher:
  1:按message调用机制本就应该是TObject的功能,因为语言提供了定义message handler的能力,自然也就应该提供调用它的能
力,否则就是不完整的。
  2:我已经说过了,Dispatch是否是virtual的无头紧要,只要有这个方法就行了。什么都没作的方法不代表不需要,abstract方
法不但什么都不作,连调用都会出错,但你不能说它就不应该存在。恰恰相反,它的意义非常重要。
  3:“类的封装应该是接口”这句话不太好理解,不过message handler正是一种定义接口的方法。你提到stategy pattern,我觉
得莫名其妙,这完全挨不上边。stategy是对算法行为进行封装的模式,而响应message则是一个对象的基本能力,完全不是一回事。
而Load Save并不是每个对象都需要的,而响应消息则是每个对象都需要的。
  4:你说“message handler的功能应该在Tcontrol及其子类中实现,如果需要message功能,可以从TControl继承而不是TObject”
我不敢苟同。TControl是VCL类库中的类,与语言无关;并且它是封装了GUI中的可视元素的基类。如果我不使用VCL,如果我写控制台
程序,如果我写一个用整数进行选择的控制台级联菜单,你是要我必须从TControl衍生还是必须用case完成呢?你又提到了MFC,这并
不能说明问题。C++不是单根继承,C++语言不支持message handler机制,与Object Pascal完全不同。

  对象的基本行为就是对消息进行响应,响应过程中可能改变自己的状态,可能向其它对象发送消息。对象对消息的响应是系统动态
结构的基本元素。常见的情况就是不同对象对同一消息响应动作不同,称之为“多态”。多态的实现方式有很多种,如
reflection,vtpr,而Object Pascal提供了vtpr和message handler两种方式。vtpr效率最高,reflection最灵活,而
message handler则介于两者之间。这正是Object Pascal语言提供的功能。如果不把Dispatch在TObject中给出一个实现,那么就只能
说它提供了一个不完整的无法使用的功能了。难道你要说:“Object Pascal提供了message handler的定义,但你不能通过message去
调用它……”或者说:“你要完整使用message handler,需要写一段汇编代码,并且可能在新的编译器中不正常……”
 
  hkbarton(宁静至远)(西南交大) ( ) 信誉:105  2003-11-13 19:17:00  得分:0
 
  怎么大家又开始争论起来啦,从昨天到现在还没有完吗?其实大家争论的目的就是要把这个问题弄明白,这样的争论是值得鼓励的
  (只要大家不要吵架),以后还应该多点这种帖子。不过我觉得从昨天 故国的那个帖子后就已经差不多了吧,答案也比较清楚了。

  firetoucher(风焱) ( ) 信誉:230  2003-11-13 21:02:00  得分:0
 
  message handler的确比较好的解决的windows中的消息问题,甚至对Unix下的信号处理也提供了很好的借鉴;而关于Object Pascal
  提供的messsage功能,我同意短歌,但这并不是问题的答案和我的疑惑,Borland的如此架构当然不会仅仅因为想大家在源程序里多
  认识一个message单词;
我的疑问是:Delphi的message handler并不适合所有的类和应用,因此并不应该出现在类的祖先TObject中。(注意:我说的是抛开了
那些又微又软的瘟东西)
1 Object Pascal并不是完全面向对象的语言,用message进行通信只是通信的一种,完全可能不用,却为什么要在TObject中固化;
2 关于类的封装,我的意思是,类应该只是封装对外通信的接口,而不是如何进行通信的方式,而如何进行通信应该是strategy做的。
就实现来讲,Dispatch可以不作为TObject的成员,而放在外边,Borland这样做可能是想提供用户重载,但有这个必要么?
3 如果说message handler对所有的TObject子类都很有用,那看看Delphi种的类库,除了从TControl继承的类,还有多少使用了
message,象TResourceStream/TList等等,而在这些类中暴露出Dispatch接口未免画足;
4 message handler是通过dynamic方法实现的,dynamic和virtual的对比不用说了,所以即使有可能在非Tcontrol子类的类中使用
message(这里不是指windows消息,而是这种模式)来通信,但dynamic的方式并不一定适合;
5 message handler是一个不错的东东,但如果不是用来处理windows这种消息,其与virtual/dynamic定义接口相比,难说其作用;
 
  plainsong(短歌) ( ) 信誉:103  2003-11-13 22:05:00  得分:0
 
  我觉得在认识message handler时应该把VCL和Object Pascal分开来看,这样就很清晰了——message handler明显属于语言的
内容而不是Framework的内容,Framework只是利用了语言提供的这种能力,它不用也不能阻止别人使用。firetoucher说把Dispatch
放在外边,这样设计我也并不反对——只要它还是语言层次提供的功能。我只是反对说它应该下降到TControl或其它非语言层次的类
中实现的说法。

  不过,假设放到外面是用全局过程来实现,势必会是这个样子:
  procedure Dispatch(self: TObject; var message);
    大家不觉得这其实是成员函数的另一种形式吗?事实上第一个C++的“编译器”就会把C++中的成员函数翻译成这样的C函数的。并
    且这种形式的Delphi“成员函数”其实我也用过,主要是因为Delphi不支持“类成员函数指针”而只支持“对象成员指针”,在
    我为一组类写一个按字符串分派的驱动表时就是用这种方式用全局函数类型代替成员函数完成的这个表。(不过这样比较的话,
    我觉得还是作为TObject的成员会更好一些,因为它访问了TObject的内部结构。)

  此外,Dispatch并不是提供给用户去重载的。正如我前面提到过的,如果不考虑照顾BCB,Dispatch完全可以声明为非多态的——
比声明成多态的更好。事实上,如果没有message handler机制,当我们需要按整数分派时,最常用的方法(我一向认为不是最好的方
法)恰好是写一个多态的Dispatch,不同的类在内部自己去分派。而使用message handler的方法则不需要,只需要在每个类中定义自
己的相应的handler,由编译器去生成一张驱动表,由语言提供的Dispatch去在表中搜索并且调用。

  按整数分派的情况是很常见的,不只是用来处理windows消息。不过message handler机制存在的价值确实不是非它不可。C++中没
有这种机制,但我一样可以实现按整数分派——不使用switch结构,而是自己生成一张驱动表(如std::map<int,void(TObject::*)
(TMessage&)>,不过又要用到class member pointer了,Object Pascal不支持,并且还要自己维护继承关系——幸好我的类关系树一般
都很矮)。工作量比用message handler稍大一些,但灵活性更好——稍作改动就可以实现按字符串或其它类型分派。当年用C进行开发
(学习)时同样写过很多“函数指针表”,日子也过的很好。

  至于你要求“对所有的子类都有用”才放到TObject中,未免太绝对化。RTTI并不是很有用的东东(我倾向于避免使用RTTI),但
TObject依然有ClassName等方法,因为提供的功能你可以不用,不提供的功能你却很难去使用它了——我是指语言层次上的能力。

  此外dynamic与virtual的对比不适用于message handler,因为它有一个按整数分派的过程,无论你用的是virtual还是dynamic还
是其它,都要有一个搜索过程(virtual比dynamic快本身就是因为少了这个过程,但现在不行了)。

返回顶部】 【打印本页】 【关闭窗口

关于我们 / 给我留言 / 版权举报 / 意见建议 / 网站编程QQ群   
Copyright ©2003- 2024 Lihuasoft.net webmaster(at)lihuasoft.net 加载时间 0.0063