象C++中:
  祖先类::方法
在Delphi中有没有对应的功能?

解决方案 »

  1.   

    在delphi中,子类已经有父类的方法啊!
      

  2.   

    例如:
    T1 = class
      procedure Test;virtual;
    end;T2 = class(T1)
      procedure Test;override;
    end;T3 = class(T2)
      procedure Test;override;
    end;...procedure T3.Test;
    begin
      //如何调用T1的Test方法?
    end;
      

  3.   

    我刚才试了一下,用BASM可以做到procedure T3.Test;
    asm
      call T1.Test
    end;如果T3.Test里面还有其他Pascal语句,那么可以procedure T3.Test;
    begin
      ...
      asm
        mov EAX, Self
        call T1.Test
      end;
      ...
    end;不过这种方法是很笨的,当例程不同的时候,自己要处理参数传递,还有就对于某些类函数,
    还要遵循他们的参数(寄存器)约定,比如constructor Create供参考
      

  4.   

    alphax(多喝了三五杯):
    如果T3.Test里面还有其他Pascal语句,为什么要增加
      mov EAX, Self
      语句?
    另外还有别的非汇编的办法解决这个问题吗?
      

  5.   

    >>如果T3.Test里面还有其他Pascal语句,为什么要增加
    >>  mov EAX, Self
    >>  语句?这主要是指asm代码前面有其他pascal代码的情况,比如
    var
     C: Integer;procedure T3.Test;
    var
      I: Integer;
    begin
      for I := 1 to 10 do Inc(C, I);  asm
        mov eax, Self
        call T1.Test
      end;
    end;在上面代码,在执行到asm关键字的时候,eax的值已经被破坏了,而Delphi约定,在对象的方法里面,EAX的值用来存放对象的实例地址,如果这个地址被破坏,将会导致后面的一系列涉及到对象实例的语句都不能正确执行,那么,怎么办呢?Delphi有自己的解决办法,就是Self。当在函数体内出现Self变量的时候,Delphi会在入口代码里面加上保存EAX的代码,通常保存在[ebp-4]的位置,这就是Self引用的地址。那么,当我们估计eax可能会被破坏时,我们可以
    用Self来恢复eax的值。当然,如果你能肯定前面的代码不会破坏eax的值,或者eax被破坏后又被恢复了,那么你可以省略mov EAX, Self这一步。不过这样做估计只有你自己清楚,甚至有时候你自己修改代码的
    时候也会忘记原来的约定,所以,安全起见,代码比较复杂的时候,不要吝惜这一步。
    >>另外还有别的非汇编的办法解决这个问题吗?我只是猜,估计是没有。因为Delphi的帮助没有提到这个内容,仅仅有的是inherited,——调用父类的方法。错了请指正
      

  6.   

    还有一种,是老达摩教我的,非汇编的方法type
     TTestProc = procedure of object;procedure T3.Test;
    var
      TP: TMethod;
    begin
      TP.Code := @T1.Test;
      TP.Data := Self;  TTestProc(TP)();
    end;
      

  7.   

    alphax(多喝了三五杯) :
    非常佩服!
      TP.Code := @T1.Test;
      TP.Data := Self;
    采用这样的办法就意味着可以调用任意类的任意方法?而且TP.Data是怎样一种处理机制?
      

  8.   

    我也是学别人的,呵呵--采用这样的办法就意味着可以调用任意类的任意方法?
    基本上可以这么说,只要你知道任意类的任意方法的地址、该类实例的指针和方法的原型--而且TP.Data是怎样一种处理机制?
    没听明白你具体想问什么
      

  9.   

    --为什么要给TP.Data赋上Self?
    这也是一种约定,Delphi在调用对象的方法,总是取方法的Data作为EAX的值,或者说TMethod.Data总是用来存放Self的
      

  10.   


    1)TProcedure
    2)TMethod
    3)procedure (a: Integer);
    4)Procedure (a: Integer) of Object;
    的区别是什么?
      

  11.   

    procedure (a: Integer);是一个普通的例程的原型,在调用这种原型的过程时,编译器不
    做特殊的处理,
    而procedure (a: Integer) of object是一个类对象的方法的原型,调用这种原型的时候,
    编译器会对参数做一点调整,虽然TProcedure是一个普通例程类型,但是他和procedure (a: Integer);是不兼容的,因
    为它的原型是procedure ;,没有参数的比如,你以下这样的assignment会被编译器拒绝
    procedure Proc(a: Integer);
    begin
      ...
    end;var
      P: TProcedure;
    ...
      P := Proc; //原型不匹配但如果,
    type
      TProcA = procedure (a: Integer);则,可以
    var
      P: TProcA;...
      P := Proc;
    TMethod比较特殊,它是一个结构,揭示了事件或者方法在对象实例的内部存储结构
    事实上他和类方法不是一回事,类方法我们可以用一个指针来表示或者引用,但是TMethod
    还有一个字段,就是Data,已经说过了,它是用来存储Self的,那为什么要存储Self呢?
    主要是为了支持事件机制,比如有
    type
      TPublisher = class(TObject)
      private
        fChangeNotify: TNotifyEvent;
        procedure FireEvent;
      public
        property ChangeNotify: TNotifyEvent read fChangeNotify write fChangeNotify;
      end;  TSubscriber = class(TObject)
      private
        fChangeCount: Integer;
        procedure WhenPublisherChange;
      public
        procedure DoSubscribe(aPublisher: TPublisher);
      end;implemenationprocedure TPublisher.FireEvent;
    begin
      if Assigned(fChangeNotify) then fChagneNotify(Self);
    end;procedure TSubscriber.WhenPublisherChange;
    begin
      Inc(fChangeCount);
    end;procedure  TSubscriber.DoSubscribe(aPublisher: TPublisher);
    begin
      aPublisher.ChangeNotify := WhenPublisherChange; //订阅变动通知
    end;我们来看看TPublisher类的对象的实例的内存映像:
    TPublisherInstanceImage = packed record
      VMTPtr: Pointer;
      AlignStub: array[0..3] of Byte;             //为了使fChange在8字节边界上对齐
      fChange: TMethod;
    end;
    PPublisherInstanceImage = ^TPublisherInstanceImage;而TSubscriber的大概是这样:
    TSubscriberInstanceImage = packed record
      VMTPtr: Pointer;
      fChangeCount: Integer;
    end;
    PSubscriberInstanceImage = ^TSubscriberInstanceImage;看一下TSubscriber.DoSubscriber的实际操作
    ///////DoSubscriber伪代码
    PPublisherInstanceImage(aPublisher)^.fChange.Code :=
        @TSubscriber.WhenPublisherChange; //将方法入口地址保存在CodePPublisherInstanceImage(aPublisher)^.fChange.Data :=
        Self; //将实例地址保存在Data
    //////当publisher触发事件(FireEvent)时,
    /////FireEvent伪代码
    if TMethod(fChange).Code <> nil then  //实际上现行的Delphi只是判断
                                          //TMethod(fChange).Code的高字节
                                          //因为实际上代码的地址总是大于$0000FFFF的
                                          //一般在$00401000以上的地址
    asm
      mov eax, TMethod(fChange).Data      //将原来从DoSubscriber中获得的对象实例放回eax
      mov edx, Self                       //TNotifyEvent的表面上的第一个参数Sender
      call TMethod(fChange).Code  //我们发现,方法的实际第一个参数总是对象的实例,这就是我前面所说的编译器会对
      //参数进行一些调整,这就是它的隐含的动作
    end;
    /////前面说过了,在方法例程的入口处,eax总是用来代表实例的首地址的,如果Delphi它不在
    fChange中保存方法的实例地址,那么它无法完成事件回调这个任务,因为TSubscriber.WhenPublisherChange的代码类似于下面,如果eax的值不正确的话,程序
    不能正确执行:
    //////伪代码
    asm
      lea eax, [eax + offset TSubsciber.fChangeCount]
      inc ; eax
      ret
    end;
    /////
    再来看看procedure of object和procedure有什么区别,比较一下下面的FireEventProc
    和上面的TPublisher.FireEvent:procedure AEvent(aSender: TObject);
    begin
    end;procedure FireEventProc;
    var
      SomeObj: TObject;
    begin
      ......
      AEvent(SomeObj); //看看实际的操作是什么?
    end;很简单
    /////FireEventProc的伪代码
    asm
      mov eax, SomeObj            //绑第一个参数SomeObj
      call AEvent                 //调用AEvent
    end;
    //////我们看见,在调用AEvent时,实际的参数的binding是比较普通的让我们来做一个小试验,我们用普通过程来模拟类方法(事件)unit Unit1;interfaceuses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs;type
      TForm1 = class(TForm)
        procedure FormCreate(Sender: TObject);
        procedure FormDblClick(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;var
      Form1: TForm1;implementation{$R *.dfm}procedure Form1_FormDblClick(aSelf: TForm1; aSender: TObject);
    begin
      aSelf.Caption := 'Double click me!!!';
      aSelf.OnDblClick := aSelf.FormDblClick;
    end;procedure TForm1.FormCreate(Sender: TObject);
    var
      M: TMethod;
      P: Pointer;
    begin
      M.Code := @Form1_FormDblClick;
      M.Data := Self;
      OnDblClick := TNotifyEvent(M);
    end;procedure TForm1.FormDblClick(Sender: TObject);
    begin
      Caption := 'The original event is restored.';
    end;end.
    试一下,运行的时候双击一下窗体,标题就会发生变动,然后再双击一下,你就明白我的苍
    白无力的语言想说的是什么了