-----如何在子类中调用任意祖先类的方法-----

snowfog 2003-04-02 10:34:55
象C++中:
祖先类::方法
在Delphi中有没有对应的功能?
...全文
178 17 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
17 条回复
切换为时间正序
请发表友善的回复…
发表回复
snowfog 2003-09-25
  • 打赏
  • 举报
回复
结帖
alphax 2003-04-18
  • 打赏
  • 举报
回复
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;

implemenation

procedure 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; //将方法入口地址保存在Code

PPublisherInstanceImage(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;

interface

uses
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.


试一下,运行的时候双击一下窗体,标题就会发生变动,然后再双击一下,你就明白我的苍
白无力的语言想说的是什么了
snowfog 2003-04-17
  • 打赏
  • 举报
回复

1)TProcedure
2)TMethod
3)procedure (a: Integer);
4)Procedure (a: Integer) of Object;
的区别是什么?
alphax 2003-04-15
  • 打赏
  • 举报
回复
--为什么要给TP.Data赋上Self?
这也是一种约定,Delphi在调用对象的方法,总是取方法的Data作为EAX的值,或者说TMethod.Data总是用来存放Self的
lifejoy 2003-04-15
  • 打赏
  • 举报
回复
其实就是指针的转换:)
snowfog 2003-04-15
  • 打赏
  • 举报
回复
为什么要给TP.Data赋上Self?
alphax 2003-04-11
  • 打赏
  • 举报
回复
我也是学别人的,呵呵

--采用这样的办法就意味着可以调用任意类的任意方法?
基本上可以这么说,只要你知道任意类的任意方法的地址、该类实例的指针和方法的原型

--而且TP.Data是怎样一种处理机制?
没听明白你具体想问什么
snowfog 2003-04-11
  • 打赏
  • 举报
回复
alphax(多喝了三五杯) :
非常佩服!
TP.Code := @T1.Test;
TP.Data := Self;
采用这样的办法就意味着可以调用任意类的任意方法?而且TP.Data是怎样一种处理机制?
alphax 2003-04-09
  • 打赏
  • 举报
回复
还有一种,是老达摩教我的,非汇编的方法

type
TTestProc = procedure of object;

procedure T3.Test;
var
TP: TMethod;
begin
TP.Code := @T1.Test;
TP.Data := Self;

TTestProc(TP)();
end;
snowfog 2003-04-03
  • 打赏
  • 举报
回复
alphax(多喝了三五杯):
如果T3.Test里面还有其他Pascal语句,为什么要增加
mov EAX, Self
语句?
另外还有别的非汇编的办法解决这个问题吗?
alphax 2003-04-03
  • 打赏
  • 举报
回复
>>如果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,——调用父类的方法。



错了请指正
x_yiwen 2003-04-02
  • 打赏
  • 举报
回复
理解错了:(
x_yiwen 2003-04-02
  • 打赏
  • 举报
回复
多态
alphax 2003-04-02
  • 打赏
  • 举报
回复
我刚才试了一下,用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

供参考
alphax 2003-04-02
  • 打赏
  • 举报
回复
mark
snowfog 2003-04-02
  • 打赏
  • 举报
回复
例如:
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;
wushenshui 2003-04-02
  • 打赏
  • 举报
回复
在delphi中,子类已经有父类的方法啊!

5,928

社区成员

发帖
与我相关
我的任务
社区描述
Delphi 开发及应用
社区管理员
  • VCL组件开发及应用社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧