首页  编辑  

重名成员的访问

Tags: /超级猛料/Language.Object Pascal/面向对象和类、VCL核心/   Date Created:

重名成员的访问

我们知道,在C++中,访问父类的同名的成员很容易,只要使用a.Parent::b这样的代码就可以了。例如

class TA

{

 public

    char *Text;

}

class TB:public TA

{

 public

   char *Text;

}

TB:b;

那么要访问父类的Text,可以这样即可:b.TA::Text,而在Delphi中,如何来访问父类的成员呢?答案是类型强制!

TA=class

public

 Text:string;

end;

TB=class(TA)

public

 Text:string;

end;

var

 b:TB;

...

 TA(b).Text:='Text TA';  ///访问父类的成员

完整的测试代码如下:

program Project1 ;

{$APPTYPE CONSOLE}

uses SysUtils ;

type

 TA = class

    public

     FName : string ;

  end ;

 TB = Class ( TA )

    public

     FName : string ;

  end ;

var

 b : TB ;

begin

  // Insert user code here

 b := TB . Create ;

 b . FName := 'abc' ;

 TA ( B ). FName := 'def' ;

 writeln ( 'TB.Name=' , b . FName , '     TB.TA.Name=' , TA ( b ). FName );

 readln ;

end .

---------------------------------------------------------------------------------------------------------------------

类的继承---跨过中间类,继承祖先的方法

有3个类 A,B,C,继承关系 A-->B-->C,

A中有一个虚拟对象方法 Fun、B、C分别重载这个方法,

现在C要继承 A.Fun 而不是 B.Fun.

如何才能做到?

type

 A = class

 public

   procedure Fun; virtual;

 end;

 B = class(A)

 public

   procedure Fun; override;

 end;

 C = class(B)

 public

   procedure Fun; override;

 end;

implementation

procedure A.Fun;

begin

 ShowMessage('A');

end;

procedure B.Fun;

begin

 ShowMessage('B');

end;

procedure C.Fun;

begin

 //在这里调用A中的Fun,怎么可以做到???

 //这样试过:

 //1  (Self as A).Fun 可以编译通过,但结果不对。

 //2  A(Self).Fun;    可以编译通过,但结果不对。

end;

回复人: findcsdn(findcsdn) ( ) 信誉:106  2002-11-22 10:49:30  得分:12

//这样写就可以了。

procedure C.Fun;

var

 tmp: pointer;

begin

 tmp := A;

 asm

   mov eax, self

   mov edx, [tmp]

   call dword ptr[edx + VMTOFFSET a.fun]

 end;

end;

回复人: xzgyb(老达摩) ( ) 信誉:110  2002-11-22 10:57:51  得分:0  

louislingjjw(云 意) :

asm

call TA.Fun

end;

就是调用直接调用TA.Fun

直接写这条指令就会直接跑到TA.Fun处执行

如果

TA.Fun里有对成员的操作出错是因为这时EAX为零

TA.Fun对数据成员的操作是根据self的偏移量执行的,在这里这个self

也就是EAX

改成这样

procedure TC.Fun(s: string);

begin

 asm

   push eax

   mov eax, self

   call TA.Fun

   pop eax

 end;

end;

可以出来你要的那个效果,主要是存一下EAX值

刚学了点汇编,瞎写的,也不知对不对,有没有什么别的副作用

回复人: xzgyb(老达摩) ( ) 信誉:110  2002-11-22 15:14:59  得分:0

 

摩托兄:

 呵呵,我只是瞎试的,不知道对不对

 而且跟FindCsdn学了一招

 VMTOFFSET和DMTINDEX指令

 VMTOFFSET返回虚方法在虚表中的偏移量

 DMTINDEX返回动态方法的Index值

而且帮助中的那个例子也挺有意思

program Project2;

type

 TExample = class

   procedure DynamicMethod; dynamic;

   procedure VirtualMethod; virtual;

 end;

procedure TExample.DynamicMethod;

begin

end;

procedure TExample.VirtualMethod;

begin

end;

procedure CallDynamicMethod(e: TExample);

asm

 // Save ESI register

 PUSH    ESI

 // Instance pointer needs to be in EAX

 MOV     EAX, e

 // DMT entry index needs to be in (E)SI

 MOV     ESI, DMTINDEX TExample.DynamicMethod

 // Now call the method

 CALL    System.@CallDynaInst

 // Restore ESI register

 POP ESI

end;

procedure CallVirtualMethod(e: TExample);

asm

 // Instance pointer needs to be in EAX

 MOV     EAX, e

 // Retrieve VMT table entry

 MOV     EDX, [EAX]

 // Now call the method at offset VMTOFFSET

 CALL    DWORD PTR [EDX + VMTOFFSET TExample.VirtualMethod]

end;

var

 e: TExample;

begin

 e := TExample.Create;

 try

   CallDynamicMethod(e);

   CallVirtualMethod(e);

 finally

   e.Free;

 end;

end.

goodloop:

 就是这样

TTest = class

 private

   FTest: Integer;

   FTest1: Integer;

 public

   constructor Create;

   procedure ShowTest;

 end;

...

implementation

constructor TTest.Create;

begin

 FTest := 20;

 FTest1 := 30;

end;

procedure TTest.ShowTest;

begin

 ShowMessage(IntToStr(FTest));

 ShowMessage(IntToStr(FTest1));

end;

procedure TMainForm.Button2Click(Sender: TObject);

var

 a: TTest;

begin

 a := TTest.Create;

 a.ShowTest;

 a.Free;

end;

 

对于 a := TTest.Create;汇编如下

mov dl, $01

mov eax, [$0046afbc]

call TTest.Create

mov ebx, eax   //ebx保存了实例的引用值

a.ShowTest 如下

mov eax, ebx

call TTest.ShowTest

因为object pascal函数的调用默认是Fastcall 的,也就是参数先传到EAX,EDX,ECX,剩下的在压栈,而在类的函数里用的self值其实是隐藏的传过来

的第一个参数,也就是EAX里的值

在看一下TTest.ShowTest

...

mov ebx,eax

...

//对应于ShowMessage(IntToStr(FTest))

lea eax, [ebp-$04]  //这是一个临时的字符串变量

mov edx, [ebx+$04]

call IntToStr

//[ebx+$04]即为FTest的值,[ebx]为vptr, [ebx+$08]为FTest1, 所以是访问类里的数据成员是通过相对于self的值的偏移量来访问

这些可以打开cpu窗口来察看

回复人: louislingjjw(云 意) ( ) 信誉:100  2002-11-28 9:06:01  得分:0

 

谢谢各位的热心帮助!

这个问题几天前已经解决了,方法有四种,其中有些原理是一样的。

方法一:(指针)

1)

procedure C.Fun;

var

 P: Pointer;

begin

 P := Pointer(ClassParent.ClassParent);

 //获得祖先类虚拟方法表入口地址;

 if Integer(P) <> 0 then

   A(Integer(P) - 76).Fun;              

 //取得self指针,强制类型转换.

end;

2)

 只要知道Self指针就指向了虚拟方法表的入口, Self指针负的偏移量是

 一些类方法和RTTI信息的地址就行了

procedure TC.fun(X: string; Y: Integer; Z: TDateTime);

var

 p: pointer;

begin

 p := Pointer(classparent.ClassParent);

 //获得祖父类虚拟方法表入口地址;

 if integer(p) <> 0 then

      ta(@p).Fun(x,y,z);                

 //取得self指针,强制类型转换.

end;

方法二:(汇编)

1) 没有参数时

procedure TC.Fun;

begin

 asm

   call TA.Fun; //直接跑到TA.Fun处执行

 end;

end;

2) 有参数时,主要是存一下 EAX 值

如果 TA.Fun 里有对成员的操作出错是因为这时 EAX 为零

TA.Fun对数据成员的操作是根据 self 的偏移量执行的,

在这里这个 self 也就是EAX改成这样

procedure TC.Fun(s: string);

begin

 asm

   push eax

   mov eax, self

   call TA.Fun

   pop eax

 end;

end;

3)

procedure C.Fun;

var

 tmp: pointer;

begin

 tmp := A;

 asm

   mov eax, self

   mov edx, [tmp]

   call dword ptr[edx + VMTOFFSET a.fun]

 end;

end;

说明:

寄存器的用法:

对于一般的 procedure ,function:

在入口:    

eax: 保存 procedure or function 的第一个参数值(如果存在的话)

edx: 保存 procedure or function 的第二个参数值

ecx: 保存 procedure or function 的第三个参数值

ebx: 保存 procedure or function 的地址  

在出口:

eax: 对于function是保存结果;对于procedure一般是保存相关的自定义错误代码

ebx: 仍是保存procedure or function的地址  

对于对象的方法:

在入口:    

eax 保存parent对象的地址

ebx 保存 procedure or function 的地址  

ecx 保存第二个参数值

edx 保存第一个参数值

在出口:

eax: 对于function是保存结果;对于procedure一般是保存相关的自定义错误代码

ebx: 仍是保存procedure or function的地址  

方法三:(参考类)

1.

   A,B两个类与C在不同的单元内,那么C中Fun不要override,因为A,B中的Fun是在Public或

Protected中,所以C的Fun中也可以用Inherited Fun,即在C中调用父类(即B)中的Fun。

如果要跨过B直接调用A中的Fun,我们一般定义一个参考类,如:

TARef=class(A)

public

 procedure Fun;

end;

...

procedure TARef.Fun;

begin

 Inherited Fun;

end;

procedure TC.Fun;

begin

 TARef(Self).Fun  

end;

2.

   更多的情况,我们只是为了要在C中改变A中的私有变量,即改变不在同一单元中类的私有

变量。解决这个问题,我们也是定义一个参考类。假设A类如下:

A=class

private

 X:Integer;

 Y:string;

....

end;

参考类则:

TARef=class             //这里老子不是A,而是A的老子

private

 X:Integer;

 Y:string;

...

end;

其实就是将A的定义搬过来,只是改了类名(注意只要私有域,不要私有方法)。

这样在C的Fun中(或其它需要的地方)可以:

 TARef(Self).X:=123;

 TARef(Self).Y:='123';

不过,这种方法有一个问题就是:一旦A的定义改了,我们的源程序也得改。这也是

每次Delphi升级时我们的程序必须检查的地方。

所谓定义参考类和指针操作private变量是一个原理。

定义的参考类其实就是为了确定你所关心的变量相对于实例入口的偏移。

和指针操作的唯一区别就是参考类方法是由编译器帮你决定访问的指针偏移量,

我的方法是由你自己计算这个指针偏移量而已。

比如要在TC中访问TA.FNum3的话:

 TA = class(Tobject)

 private

   FNum1: string;

   FNum2: Integer;

   FNum3: Float;

   FNum4: DateTime;

   FNum5: string;

   FNum6: string;

 public

   function temp1(X: string;): string; virtual;

   procedure temp2(X: Integer; Y: TDateTime): string; virtual;

   procedure fun(X: string; Y: Integer; Z: TDateTime);virtual;

 end;

 TB = class(TA)

 private

  FNote1: string;

  FNote2: TDateTime;

  FNote3: Integer;

 protected

  X1: Integer;

 public

   function temp3(S: string): string; virtual;

   procedure temp4(I: Integer): string; virtual;

   procedure fun(X: string; Y: Integer; Z: TDateTime);override;

 end;

 TC = class(TB)

 private

  FNote4: string;

  FNote5: TDateTime;

  FNote6: Integer;    

 protected

  X2: string;

 public

   function temp5(S: string): string;

   procedure temp6(S: string); string;

   procedure fun(X: string; Y: Integer; Z: TDateTime);override;

 end;

指针:

type

 TRefRecord=record      // TB里我们所要跳过的私有变量

   FNum1: string;

   FNum2: Integer;

   FNum3: Float;

 end;

 PRefRecord=^TRefRecord;

var

 pt: PRefRecord;

 c: TC;

...

pt := Pointer(Integer(c)+TObject.InstanceSize);

pt^.FNum3 := 5.5;

参考类:

 TRef = class

 private

   FNum1: string;

   FNum2: Integer;

   FNum3: Float;

 end;

var

 c: TC;

...

TRef(c).FNum3 := 5.5

这两种方法操作的都是TA.FNum3这个私有变量

同理: 要访问TB.FNote2的话

指针:

type

 TRefRecord=record

   FNote1: string;

   FNote2: TDateTime;

 end;

var

 pt: ^TRefRecord;

 c: TC;

pt := pointer(integer(c)+TA.InstanceSize);

pt^.FNote2 := now;

参考类:

 TRef = class(TA)

 private

   FNote1: string;

   FNote2: TDateTime;

 end;

var

 c: TC;

TRef(c).FNote2 := now;

方法四:(汇编+指针)

TCallFun = procedure (X: string; Y: Integer; Z: TDateTime) of object;

procedure TC.fun(X: string; Y: Integer; Z: TDateTime);

var

 m: TMethod;

 voffset: Integer;

begin

 asm

   mov voffset, VMTOFFSET TA.fun

 end;

 m.Code := Pointer(Pointer((Integer(Classparent.ClassParent) + voffset))^);

 m.Data := self;

 TCallFun(m)(X, Y, Z);

end;

注:修改父类的指针变量

//修改父类的私有变量

procedure TC.Fun(S: string);

var

 P: PString;

begin

 P := Pointer(Integer(Self) + TObject.InstanceSize);  // p 现在指向TA.FNode

 P^ := 'A' + S;

 ShowMessage(P^);

end;

//说明

这个self指的是TC的实例所在的地址,在那里数据是这样排列的:

TC.VMT入口地址

TObject所有的Private,protected,public变量

TA所有private,protected,public变量

TB所有private,protected,public变量

TC所有private,protected,public变量

...(是否还有其他的就不知道了)

这样的话要获得TA某个private变量的地址只要跳过它前面的所有变量的偏移量即可。

Integer(self)-> 入口地址 + TObject.InstanceSize-->跳过所有TObject的变量和VMT

这时指针已经指向TA第一个private变量了。这里恰好正是我们关心的FNode

接下来对这个指针所在的数据进行操作所修改的就是TA的Private变量值了。