社区
Delphi
帖子详情
-----如何在子类中调用任意祖先类的方法-----
snowfog
2003-04-02 10:34:55
象C++中:
祖先类::方法
在Delphi中有没有对应的功能?
...全文
178
17
打赏
收藏
-----如何在子类中调用任意祖先类的方法-----
象C++中: 祖先类::方法 在Delphi中有没有对应的功能?
复制链接
扫一扫
分享
转发到动态
举报
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中,子类已经有父类的方法啊!
php实现无限分
类
php实现无限分
类
无限分
类
允许我们构建
任意
深度的分
类
结构,而不限制父
类
与
子
类
的数量。以下将详细介绍如何使用PHP实现无限分
类
,并提供相关的实现思路。 1. **无限分
类
的原理** 无限分
类
通常基于树形结构,每个节点都有一个父节点...
C、C++编程题目和代码4.docx
- **多重继承在信用卡
中
的应用**:定义一个信用卡基
类
,然后定义旅游卡
类
和购物卡
类
作为信用卡的
子
类
。接着,定义一个商旅信用卡
类
,它继承旅游卡
类
和购物卡
类
,以提供更全面的服务。 ### 13. 电视机与遥控器 - **...
79.LabVIEW 面向对象程序设计的简介.doc-综合文档
- 所有LabVIEW
类
都共享一个共同的
祖先
类
,而在C++
中
,基
类
可以是
任意
类
。 - LabVIEW不支持多继承,这限制了
类
的复杂性,但也降低了设计的难度和潜在的冲突。 通过利用面向对象编程,LabVIEW用户可以构建更高效、...
JVM笔记 - JVM 实现
方法
调用
JVM笔记 - JVM 实现
方法
调用
Java 虚拟机识别
方法
的关键在于
类
名、
方法
名以及
方法
描述符(method descriptor)
方法
描述符,它是由
方法
的参数
类
型以及返回
类
型所构成 字节码
调用
指令 invokestatic:用于
调用
静态...
彻底搞懂Python
类
属性和
方法
的
调用
Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python
中
创建一个
类
和对象是很容易的。 一、
类
、对象概述 在面向对象程序设计
中
,把数据以及对数据的操作封装在一起,组成一个整体(对象),不同对象...
Delphi
5,928
社区成员
262,931
社区内容
发帖
与我相关
我的任务
Delphi
Delphi 开发及应用
复制链接
扫一扫
分享
社区描述
Delphi 开发及应用
社区管理员
加入社区
获取链接或二维码
近7日
近30日
至今
加载中
查看更多榜单
社区公告
暂无公告
试试用AI创作助手写篇文章吧
+ 用AI写文章