zoukankan      html  css  js  c++  java
  • Delphi 对象构造和vmt系列

    技术交流,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;
  • 相关阅读:
    Git的commit your changes or stash them before you can merge
    php面试题汇总一(基础篇附答案)
    php面试题汇总二(基础篇附答案)
    php面试题汇总三(基础篇附答案)
    php面试题汇总四(基础篇附答案)
    nodejs 后台服务启动
    解决failed to push some refs to
    读书计划
    spring cloud 学习
    spring IOC
  • 原文地址:https://www.cnblogs.com/huangjacky/p/1627413.html
Copyright © 2011-2022 走看看