象C++中:
祖先类::方法
在Delphi中有没有对应的功能?
祖先类::方法
在Delphi中有没有对应的功能?
解决方案 »
- TPerlRegEx如何获取匹配的结果。
- 有关QueryPerformanceFrequency的问题!
- 格式化数据 NAN .......
- 问几个简单的问题,如何在DBComboBox控件下拉列表中,动态列出某个字段的值?以及动态SQL输出参数的格式
- 如何按比例缩小图象??
- 数据库与ODBC(SQL Server)的连接参数问题
- 向高手请教:Tlabel.MouseMove与Tedit.MouseMove有什么区别?
- 急急!!!!
- 关于DBgrid数据库字段显示问题!字段显示的转换问题!
- 如何使用UrlDownloadtoFile下载文件使用进度条?
- 从form1 中怎么判断form2已经被打开了还是已经关闭了?
- panel控件的设置,很简单!
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;
asm
call T1.Test
end;如果T3.Test里面还有其他Pascal语句,那么可以procedure T3.Test;
begin
...
asm
mov EAX, Self
call T1.Test
end;
...
end;不过这种方法是很笨的,当例程不同的时候,自己要处理参数传递,还有就对于某些类函数,
还要遵循他们的参数(寄存器)约定,比如constructor Create供参考
如果T3.Test里面还有其他Pascal语句,为什么要增加
mov EAX, Self
语句?
另外还有别的非汇编的办法解决这个问题吗?
>> 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,——调用父类的方法。错了请指正
TTestProc = procedure of object;procedure T3.Test;
var
TP: TMethod;
begin
TP.Code := @T1.Test;
TP.Data := Self; TTestProc(TP)();
end;
非常佩服!
TP.Code := @T1.Test;
TP.Data := Self;
采用这样的办法就意味着可以调用任意类的任意方法?而且TP.Data是怎样一种处理机制?
基本上可以这么说,只要你知道任意类的任意方法的地址、该类实例的指针和方法的原型--而且TP.Data是怎样一种处理机制?
没听明白你具体想问什么
这也是一种约定,Delphi在调用对象的方法,总是取方法的Data作为EAX的值,或者说TMethod.Data总是用来存放Self的
1)TProcedure
2)TMethod
3)procedure (a: Integer);
4)Procedure (a: Integer) of Object;
的区别是什么?
做特殊的处理,
而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.
试一下,运行的时候双击一下窗体,标题就会发生变动,然后再双击一下,你就明白我的苍
白无力的语言想说的是什么了