现在的位置: 首页 > 综合 > 正文

Delphi – 对象构造和vmt系列

2011年02月09日 ⁄ 综合 ⁄ 共 4811字 ⁄ 字号 评论关闭

技术交流,DH讲解.

在前面2篇文章中,我们发现在TObject.InitInstance都没有IntfTable,所以有些地方的代码都没有执行.
所以下面我们把代码改一下,看看新的效果,然后把vmt系列的都来试一下:

  IHuangJacky = interface
  ['{B7D099CE-BAD5-4589-86EA-71AE78B37483}']
    procedure SayMyName;
  end;

  THuangJacky = class(TInterfacedObject,IHuangJacky)
  private
    FName:string;
    procedure SetName(const Value: string);
  public
    procedure SayMyName;
    constructor Create;
    property Name:string read FName write SetName;
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  H:THuangJacky;
begin
  H:=THuangJacky.Create();
  ShowMessage(H.Name);
  H.SayMyName;
  H.Free;
end;

一样走进入,Create里面,一样的我都不说了.
列出来不一样的地方:
InstanceSize返回是$18 = 24.上一次没有实现接口,只有12,这一次多一半.
那么这一半的内存都用来干什么了呢?
我们在进入InitInstance之前先看几个相关的结构体:

PInterfaceEntry = ^TInterfaceEntry;
  TInterfaceEntry = packed record
    IID: TGUID;
    VTable: Pointer;
    IOffset: Integer;
    ImplGetter: Integer;
  end;

  PInterfaceTable = ^TInterfaceTable;
  TInterfaceTable = packed record
    EntryCount: Integer;
    Entries: array[0..9999] of TInterfaceEntry;
  end;
  
  TGUID = packed record
    D1: LongWord;//4
    D2: Word;//2
    D3: Word;//2
    D4: array[0..7] of Byte;//8
  end;

然后InitInstance后半部分代码,因为前面都是一样的.

00404DCE 89D0             mov eax,edx
00404DD0 89E2             mov edx,esp
00404DD2 8B4BAC           mov ecx,[ebx-$54] //vmtIntfTable
00404DD5 85C9             test ecx,ecx
00404DD7 7401             jz $00404dda
00404DD9 51               push ecx //压入,说明有IntfTable.
00404DDA 8B5BD0           mov ebx,[ebx-$30] //vmtParent
00404DDD 85DB             test ebx,ebx
00404DDF 7404             jz $00404de5
00404DE1 8B1B             mov ebx,[ebx] //遍历父对象
00404DE3 EBED             jmp $00404dd2
00404DE5 39D4             cmp esp,edx
00404DE7 741D             jz $00404e06
00404DE9 5B               pop ebx //pop,第一次是父类vmtIntfTable
00404DEA 8B0B             mov ecx,[ebx] //EntryCount 有1个
00404DEC 83C304           add ebx,$04 //偏移+4,正好跳过第一个变量.
00404DEF 8B7310           mov esi,[ebx+$10] //跳到VTable处
00404DF2 85F6             test esi,esi //$00401c78 
00404DF4 7406             jz $00404dfc
00404DF6 8B7B14           mov edi,[ebx+$14] //IOffset
00404DF9 893407           mov [edi+eax],esi //将eax + 8 处写上VTable,第二次偏移是$10
00404DFC 83C31C           add ebx,$1c
00404DFF 49               dec ecx //
00404E00 75ED             jnz $00404def //看EntryCount是否为0
00404E02 39D4             cmp esp,edx
00404E04 75E3             jnz $00404de9
00404E06 5F               pop edi
00404E07 5E               pop esi
00404E08 5B               pop ebx
00404E09 C3               ret 
00404E0A 8BC0             mov eax,eax 

//首先自己类有vmtIntfTable,所以push一次ecx,$004b330c
//然后判断父类,也有vmtIntfTable,所以再push一次ecx //$00401c84

我们发现2个类的IntfTable中EntryCount只有一个,那么我们看看VTable里面都是些什么内容:
第一个TInterfacedObject的VTable:
image 
我们可以看到是3个函数地址,这3个函数都是什么呢?
image
我们看见里面记录了是TInterfacedObject里面实现了IInterface接口里面的3个函数.
第二个THuangJacky的VTable:
image 
这里有了4个函数指针.具体看看都是什么函数:
image
这4个函数就是IHuangJacky接口要实现的函数.
根据李维大哥的书里面:要在VTable里面来,接口必须要有GUID,额.是么?我们去掉IHuangJacky的GUID看一下:
image
没有变化,看来和GUID没有关系呀.难道维哥这话还有编译器版本有关.
最后看一下实例所分内存的情况:
image 
0-3:类 4-7:空 8-B:父类VTable C-F:FName 10-13:自己的VTable 14-17:空

我们来看看System.pas中定义的几个常量的意义:

{ Virtual method table entries }
  vmtSelfPtr           = -88;
  vmtIntfTable         = -84;
  vmtAutoTable         = -80;
  vmtInitTable         = -76;
  vmtTypeInfo          = -72;
  vmtFieldTable        = -68;
  vmtMethodTable       = -64;
  vmtDynamicTable      = -60;
  vmtClassName         = -56;
  vmtInstanceSize      = -52;
  vmtParent            = -48;
  vmtEquals            = -44 deprecated 'Use VMTOFFSET in asm code';
  vmtGetHashCode       = -40 deprecated 'Use VMTOFFSET in asm code';
  vmtToString          = -36 deprecated 'Use VMTOFFSET in asm code';
  vmtSafeCallException = -32 deprecated 'Use VMTOFFSET in asm code';
  vmtAfterConstruction = -28 deprecated 'Use VMTOFFSET in asm code';
  vmtBeforeDestruction = -24 deprecated 'Use VMTOFFSET in asm code';
  vmtDispatch          = -20 deprecated 'Use VMTOFFSET in asm code';
  vmtDefaultHandler    = -16 deprecated 'Use VMTOFFSET in asm code';
  vmtNewInstance       = -12 deprecated 'Use VMTOFFSET in asm code';
  vmtFreeInstance      = -8 deprecated 'Use VMTOFFSET in asm code';
  vmtDestroy           = -4 deprecated 'Use VMTOFFSET in asm code';

  vmtQueryInterface    = 0 deprecated 'Use VMTOFFSET in asm code';
  vmtAddRef            = 4 deprecated 'Use VMTOFFSET in asm code';
  vmtRelease           = 8 deprecated 'Use VMTOFFSET in asm code';
  vmtCreateObject      = 12 deprecated 'Use VMTOFFSET in asm code';

搞个代码来测试下:

procedure TForm1.Button1Click(Sender: TObject);
var
  H:THuangJacky;
  I,J:Integer;
begin
  H:=THuangJacky.Create();
  I:=PInteger(H)^; //获得THuangJacky的地址.
  J:=I - 88;//vmt系列的开头
  ShowMessageFmt('I:%8x,J:%8x',[I,J]);
  H.Free;
end;

第一个:
image 
这个就是THuangJacky的地址

改- 88 为 - 84,后看结果:
image  image image
可以看出这个是一个TInterfaceTable,1是EntryCount,中间16个0是InterfaceEntry的GUID,$004b336c应该是VTable.

改- 84 为 - 80:
image
0,代表没有不过从名字看vmtAutoTable应该是Automation会用到了,暂时没有测试.

改- 80 为 - 76:
image
这个也是类THuangJacky的地址

改 –76 为 - 72:
image image
结合名字来看这个应该是是一个PTypeInfo指针.

像上面那样一个一个试太累了,我们把代码改一下:

procedure TForm1.Button1Click(Sender: TObject);
var
  H:THuangJacky;
  I,J:Integer;
  K,L: Integer;
  F:TextFile;
  buf:array[0..23] of Byte;
begin
  H:=THuangJacky.Create();
  AssignFile(F,'C:\11.txt');
  try
    I:=PInteger(H)^; //获得THuangJacky的地址.
    if not FileExists('C:\11.txt') then
      Rewrite(F)
    else
      Append(F);
    for K := 0 to 25 do
    begin
      L:=4*k - 88;
      J:=PInteger(I + L)^;
      Writeln(F,Format('偏移:%d,值:%8x',[L,J]));
      if J<>0 then
      begin
        //Copy24个字节来看看
        CopyMemory(@buf[0],Pointer(J),24);
        Writeln(F,Format('00-07 %8x %8x',
          [PInteger(@buf[0])^,PInteger(@buf[4])^]));
        Writeln(F,Format('08-0F %8x %8x',
          [PInteger(@buf[8])^,PInteger(@buf[12])^]));
        Writeln(F,Format('10-17 %8x %8x',
          [PInteger(@buf[16])^,PInteger(@buf[20])^]));
      end;
      Writeln(F,'---------------------------------------------------------');
    end;
    Writeln(F,Format('对象指针:%8x,类指针:%8x,父类指针:%8x',
      [Integer(H),I,Integer(TInterfacedObject)]));
    Writeln(F,Format('对象VTable:%8x,父类VTable:%8',
      [PInteger(Integer(H)+16)^,PInteger(Integer(H)+8)^]));
  finally
    CloseFile(F);
    H.Free;
  end;
end;

抱歉!评论已关闭.