社区
Delphi
帖子详情
-----如何在子类中调用任意祖先类的方法-----
snowfog
2003-04-02 10:34:55
象C++中:
祖先类::方法
在Delphi中有没有对应的功能?
...全文
194
17
打赏
收藏
-----如何在子类中调用任意祖先类的方法-----
象C++中: 祖先类::方法 在Delphi中有没有对应的功能?
复制链接
扫一扫
分享
转发到动态
举报
写回复
配置赞助广告
用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中,子类已经有父类的方法啊!
php实现无限分
类
php实现无限分
类
php实现无限分
类
php实现无限分
类
php实现无限分
类
php实现无限分
类
php实现无限分
类
php实现无限分
类
php实现无限分
类
C、C++编程题目和代码4.docx
编程
79.LabVIEW 面向对象程序设计的简介.doc-综合文档
79.LabVIEW 面向对象程序设计的简介.doc
JVM笔记 - JVM 实现
方法
调用
JVM笔记 - JVM 实现
方法
调用
Java 虚拟机识别
方法
的关键在于
类
名、
方法
名以及
方法
描述符(method descriptor)
方法
描述符,它是由
方法
的参数
类
型以及返回
类
型所构成 字节码
调用
指令 invokestatic:用于
调用
静态
方法
invokespecial:用于
调用
私有实例
方法
、构造器,以及使用 super 关键字
调用
父
类
的实例
方法
或构造器,和所实现接口的默认
方法
。 invokevirtual:用于
调用
非私有实例
方法
。 invokeinterface:用于
调用
接口
方法
。
AI大模型入门1.3-python基础-
类
也不使用父
类
名称
调用
,则会输出D-B,停止到不继续
调用
的
类
,这意味着在
子
类
调用
某个
方法
时,super()函数只是按照MRO算法的顺序去寻找这个
方法
,找到之后会执行;注意一个特殊情况,即
子
类
实例
调用
父
类
的其
中
一个
方法
A,该
方法
调用
父
类
的另一个
方法
B,而
子
类
重写了
方法
B,那么
子
类
实例
调用
方法
A的时候最终也会
调用
子
类
的
方法
B。前面介绍MRO算法的时候讲到,它可以解决多继承
中
的
方法
调用
顺序问题,事实上,MRO算法只是提供了继承关系的解析顺序,而真正按照这个顺序完成
方法
调用
的是super()函数。
Delphi
5,935
社区成员
262,943
社区内容
发帖
与我相关
我的任务
Delphi
Delphi 开发及应用
复制链接
扫一扫
分享
社区描述
Delphi 开发及应用
社区管理员
加入社区
获取链接或二维码
近7日
近30日
至今
加载中
查看更多榜单
社区公告
暂无公告
试试用AI创作助手写篇文章吧
+ 用AI写文章