Inside VCL:接口指针调用函数的时候,如何获得对象指针以完成函数调用?

Inside VCL:接口指针调用函数的时候,如果获得对象指针以完成函数调用?

 

对于Delphi中的对象方法,大家都比较清楚其与一般方法的区别。如果不知道的我们也先了解一下。对象方法相对于一般的方法,会多出一个隐含参数Self,因此对于Form1的一个过程:

        procedure TForm1.Button1Click(Sender: TObject);

  如果不在对象中申明的话,其完整的申明应该是这样的:

         procedure Button1Click(Self: TForm1; Sender: TObject);

  

  对于上面的详细细节不再讲述。很多Delphi的书籍都讲到这点。下面我将默认您已经了解了这点。

  针对上面的知识,我们申明一个类 TA,其实现了接口IAIA有一个方法叫F

 

  IA = interface

  ['{CAD3FF7B -6C 23 -41A 3-AA71-16B4E8D9D35D}']

    procedure F;

  end;

 

  TA = class( TInterfacedObject , IA )

  protected

    procedure F;

  end;

 

  那么,做如下调用:

var

  a: TA;

  ai: IA;

begin

  a := TA.Create;

  a.F;

 

  ai := a as IA;

  ai.F;

end;

 

我的问题是,我们都知道,在方法F实现的时候,其必然有一个参数Self传入,而这个Self必然是对象指针。

当进行a.F调用的时候,显然a可以作为Self指针传入。但是,当进行ai.F调用的时候,Delphi是如何获得对象指针,并传入F函数的呢?我们知道,ai并没有提供方法去获取对象方法。

 

简单点说:接口指针调用函数的时候,如何获得对象指针以完成函数调用?

 

要回答这个问题,有两条线索:

1.   跟踪ai.F调用的汇编代码

2.   跟踪TObject实现接口的细节代码。(Delphi提供了Pure Pascal代码,真好)

 

我先对线索2进行了跟踪:

对于一个实现了接口的对象来讲,在初始化创建对象的时候,必须初始化好其内存结构,通过源码可以发现在NewInstance的时候会调用InitInstance

下面Delphi提供的Pure Pascal

class function TObject.InitInstance(Instance: Pointer): TObject;

var

  IntfTable: PInterfaceTable;

  ClassPtr: TClass;

  I: Integer;

begin

  FillChar(Instance^, InstanceSize, 0);

  PInteger(Instance)^ := Integer(Self);

  ClassPtr := Self;

  while ClassPtr <> nil do

  begin

    IntfTable := ClassPtr.GetInterfaceTable;

    if IntfTable <> nil then

      for I := 0 to IntfTable.EntryCount-1 do

  with IntfTable.Entries[I] do

  begin

    if VTable <> nil then

      PInteger(@PChar(Instance)[IOffset])^ := Integer(VTable);   // 注意这里,将在对象空间中设置指针

  end;

    ClassPtr := ClassPtr.ClassParent;

  end;

  Result := Instance;

end;

 

阅读上面的代码,可以发现,Object在初始化的时候,会先得到所有实现的接口列表,然后针对每个接口,会在Instance中对应一个IOffset位置上,存放一个VTable的指针。

简单点说:一个对象没实现一个接口,会在对象控件中增加一个4字节指针。这个结论可以通过验证InstanceSize来确认。而这个4字节指针指向这个接口的方法表。

 

细心的您,可能已经发现,不对啊,这个VTable,对于所有对象都是一个地址!因此在这里绝对不可能获取我们“可变”的对象实例指针。

不过既然说到这里,我们应该重新认识一下对象指针和接口指针的实质。

 

对象实例一般情况下,是建立在堆上的连续空间,对象指针会指向这段连续空间的首地址。接口指针和对象指针不是一个指针,它指向这个实例空间的某个IOffSet。如下简图所示:

Object Pointer         Interface Pointer

/ ß   IOffSet    –>   /

Object Instance

 

我们的问题并没有回答,虽然我们在表面上能够认识到a就是对象指针,ai就是接口指针,其间的相对位置是能够得到的。但是程序是如何获得的呢?是不是申明地方存储着这两个指针的偏移地址呢?

 

结合一下我们刚才的初始化代码,我们可以发现,可以实现一段代码来通过接口指针来得到对象指针!

function IntfToObj(AClass: TClass; AIntf: IInterface; AIID: TGUID): TObject;

var

  rIntfEntry: PInterfaceEntry;

begin

  rIntfEntry := AClass.GetInterfaceEntry(AIID);

  Assert(rIntfEntry.IOffset <> 0);  // 找到AIID定义的接口

  Result := TObject(Integer(AIntf)- rIntfEntry.IOffset)

end;

方法很简单,取得偏移地址,偏移指针!

这真的是一件很振奋的结论,在很多应用中,我们就曾经要过这样的函数,可以让我们在使用接口的同时,也可以使用对象的一些方法。

可惜的是我们还没有回答接口是如何在程序中得到的。显然,Delphi并没有提供这样的函数。我们必须针对第一条线索进行跟踪!

a.Fai.F分别下断点,调试。调出CPU窗体,看到如下代码:

Unit1.pas.114: a.F;

0045C 13E 8BC3             mov eax,ebx

0045C 140 E8CFFFFFFF       call TA.F

Unit1.pas.117: ai.F;

0045C 15B 8B45FC           mov eax,[ebp-$04]

0045C 15E 8B10             mov edx,[eax]

0045C 160 FF 520C            call dword ptr [edx+$ 0c ]

显然,最后调用的函数并不一样,重点观察ai.F的调用:

1.       先将ai指针存放到EAX寄存器

2.       跳转到[edx+$ 0c ]所指向的代码

 

我们看看这段代码其实是什么。

0045BD49 83C 0F 4           add eax,-$ 0c

0045BD 4C E 9C 3030000       jmp TA.F

相信大家看到了熟悉的TA.F了,再看看上面那句话!

Add EAX, -$ 0c ,不就是我们上面那个IntfToObj的函数实现的结果吗?看来编译器在编译的时候,为了加快速度,直接将偏移地址放在了代码里。终于知道Delphi是如何实现接口到对象的转换了。

 

分析到这里,我们总结一下会发现,IntfToObj的缺点是不能针对任意类实现的接口进行计算。如果两个类都实现了同一个接口I,那么你不能简单用那个方法去获取偏移,因为两个类类型可能完全没关系!

而上面这段汇编却给了我们启示。既然CPU能知道偏移,我们也可以直接通过ai指针来获取啊!至于如何分析汇编,那就是另一篇文章了。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
《深入核心——VCL架构剖析》光盘说明-、光盘用途 本光盘为《深入核心——VCL架构剖析》一书的配套光盘,供读者阅读图书时参考和学习。二、光盘内容 光盘“源代码”目录中包含了书中所有源代码,文件目录和图书的目录相对应。如“Chap01”表示书中第1章的范例源代码。 光盘包含了全部的pas、dfm和dpr文件。 我们已经对所有文件进行了简体化工作。如果您在使用中发现有界面乱码问题,请将窗体Font改为“宋体”,Charset改为gb2312即可,并请即时告知我们,让更多读者受益。三、运行环境 多数代码可以直接在Delphi6和Delphi7环境下运行。部分涉及.NET技术内容的代码,需要在Delphi 7上安装Borland .NET Complier for Delphi编译器方可编译执行。Borland已经正式推出Delphi 8 for .NET,所以本书范例中部分内容可能与D8最终版本不符。四、使用方法 直接将范例文件拷贝至硬盘适当目录即可。 多数完整应用程序代码已经编译为.exe可执行文件,读者可直接运行之。五、防病毒 本光盘所有文件都已经过Norton Antivirus扫描,未发现有任何已知病毒。六、风险 读者须对使用光盘所附代码、文件所造成的一切后果负责。 七、如果对代码有任何疑问、建议或者发现有遗漏、错误之处请与 [email protected]联系。六、所有源代码可以在学习和工作中直接使用,但请不要用于商业目的。
李维新书<<Inside VCL>>的前两章预读,12月份出版。《Inside VCL(深入核心——VCL架构剖析)》将带领您:• 领略优秀Framework之大局观!追寻软件架构大师设计思路,高屋建瓴,廓清Framework设计要义。告诉您何谓Framework,VCL是一种什么样的Framework,未来VCL Framework将走向何方。它是Delphi程序员不可不读的一部大书。• 剖析VCL之精妙大架构!深入探究VCL设计与实现,以RPG方式与读者共同扮演架构设计师,一砖一瓦搭起VCL大厦。配合Windows Framework、COM、Interface等各种技术,按图索骥,逐步厘清VCL Framework真正的脉络所在。• 掌握OO开发之大奥义!紧密融合OO理念和设计模式,用OO理念重新考量VCL,指出其中优秀和不妥之处;让您在了解VCL Framework的同时,体验源自顶尖程序员的OO观。 Delphi已经推出了7个版本,在未来也会持续的推出新的版本,许多人可能也已经使用Delphi许多年并且开发了各种不同的应用系统,但是不管我们使用了Delphi多久,我们真的已经了解Delphi,而且发挥Delphi十成的功能了吗?Delphi 1从推出以来,在每一个版本都加入了许多新的功能,融合的软件技术也一直在增加之中。从RAD、Flat-File数据库功能、VCL组件,一直到主从架构、Web、COM/COM+、MIDAS、多层分布式应用系统,到现在的SOAP/Web Service、dbExpress,DataSnap等技术,程序员们不断地学习和使用新的技术,以便用来开发新的应用系统或是增加程序员个人的附加价值,以求在信息领域能够更上一层楼。不过这些技术大都是属于“应用类”。... ...

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值