• Delphi 对象构造浅析


    自己用上了D2010,然后心中一直想着一句话就是Borland的编译器比其他的都要好很多,虽然现在Delphi已经易主了.
    我们现在一天都在说面向对象,但是我们知道对象在内存都是一堆数据而已,那么Delphi编译器是怎么来管理这些数据的呢?
    抱着这样的态度,我用简单的代码进行了一些测试,当然技术有限,有所错误,希望朋友指出来不要喷我.
    本文首发http://huangjacky.cnblogs.com/,转载请注明,谢谢.
    好开始,本文是以D2010为基础的.其他版本上面可能会有所不同.

    代码
      1 编译器创建对象的过程:
      2  假设有这样一个类:
      3  THuangJacky= class
      4 
      5   end;
      6   
      7   我们创建一个对象:
      8   procedure TForm3.btn1Click(Sender: TObject);
      9  var
     10    A:THuangJacky;
     11  begin
     12    A:=THuangJacky.Create;
     13    A.Free;
     14  end
     15  
     16 看看编译器都在干什么?
     17 Unit3.pas.34: A:=THuangJacky.Create;
     18 //这个Dl为什么要设置成1?
     19 004A3BE0 B201             mov dl,$01
     20 //$004a3b54这个就是THuangJacky类的地址
     21 004A3BE2 A1543B4A00       mov eax,[$004a3b54]
     22 //由于我们没有在构造方法里面写其他代码所以这里就直接调用TObject.Create
     23 004A3BE7 E88C11F6FF       call TObject.Create
     24 ----------------------------------------------------------
     25 TObject.Create:
     26 00404D78 84D2             test dl,dl
     27 00404D7A 7408             jz $00404d84 //如果dl为0就跳--------------------|
     28 00404D7C 83C4F0           add esp,-$10 //分配堆栈空间给局部变量.            |
     29 00404D7F E840050000       call @ClassCreate //System.ClassCreate         |
     30 00404D84 84D2             test dl,dl                                   <-|   
     31 00404D86 740F             jz $00404d97 //为0 跳出构造函数-----------------------|
     32 00404D88 E88F050000       call @AfterConstruction                             |
     33 00404D8D 648F0500000000   pop dword ptr fs:[$00000000]//还原SEH                |
     34 00404D94 83C40C           add esp,$0c //堆栈平衡                               | 
     35 00404D97 C3               ret                                               <-| 
     36 ----------------------------------------------------------------------------------
     37 @ClassCreate:
     38 004052C4 52               push edx
     39 004052C5 51               push ecx
     40 004052C6 53               push ebx
     41 004052C7 84D2             test dl,dl
     42 004052C9 7C03             jl $004052ce //dl小于0跳----------------------|
     43 004052CB FF50F4           call dword ptr [eax-$0c]//这个是NewInstance   |
     44 004052CE 31D2             xor edx,edx //清空edx=0                     <-|
     45 004052D0 8D4C2410         lea ecx,[esp+$10]
     46 004052D4 648B1A           mov ebx,fs:[edx] //这里应该是SEH的操作,和构造无关
     47 004052D7 8919             mov [ecx],ebx
     48 004052D9 896908           mov [ecx+$08],ebp
     49 004052DC C74104ED524000   mov [ecx+$04],$004052ed
     50 004052E3 89410C           mov [ecx+$0c],eax
     51 004052E6 64890A           mov fs:[edx],ecx
     52 004052E9 5B               pop ebx
     53 004052EA 59               pop ecx
     54 004052EB 5A               pop edx
     55 004052EC C3               ret 
     56 004052ED E9E2010000       jmp @HandleAnyException //如果发生异常那么就会调用到这里
     57 004052F2 8B44242C         mov eax,[esp+$2c]
     58 004052F6 8B400C           mov eax,[eax+$0c]
     59 004052F9 85C0             test eax,eax
     60 004052FB 740E             jz $0040530b
     61 004052FD 8B08             mov ecx,[eax]
     62 004052FF B281             mov dl,$81
     63 00405301 50               push eax
     64 00405302 FF51FC           call dword ptr [ecx-$04]
     65 00405305 58               pop eax
     66 00405306 E809000000       call @ClassDestroy
     67 0040530B E8C8050000       call @RaiseAgain
     68 00405310 C3               ret 
     69 00405311 8D4000           lea eax,[eax+$00]
     70 -----------------------------------------------------------------
     71 TObject.NewInstance:
     72 00404D40 53               push ebx
     73 00404D41 8BD8             mov ebx,eax //在ebx中保存THuangJacky类指针
     74 00404D43 8BC3             mov eax,ebx //因为InstanceSize返回值会覆盖eax
     75 00404D45 E826000000       call TObject.InstanceSize
     76 00404D4A E885F4FFFF       call @GetMem //分配内存
     77 00404D4F 8BD0             mov edx,eax  //edx=$00eb0ec0,分配的内存块
     78 00404D51 8BC3             mov eax,ebx  //eax=$004a3b54,类指针
     79 00404D53 E85C000000       call TObject.InitInstance
     80 00404D58 5B               pop ebx
     81 00404D59 C3               ret 
     82 00404D5A 8BC0             mov eax,eax
     83 ------------------------------------------------------------------
     84 TObject.InstanceSize的pascal形式:
     85 class function TObject.InstanceSize: Longint;
     86 begin
     87   Result := PInteger(Integer(Self) + vmtInstanceSize)^;
     88 end;
     89 就是类指针-52的地址,类指针是多少?刚才赋值给Eax的那个值$004a3b54
     90 汇编代码形式:
     91 TObject.InstanceSize:
     92 00404D70 83C0CC           add eax,-$34   //减去52
     93 00404D73 8B00             mov eax,[eax]  //指向的值传给eax返回,可以看到是8
     94 00404D75 C3               ret            //退出
     95 00404D76 8BC0             mov eax,eax
     96 -----------------------------------------------------------------
     97 接下来该分配内存了
     98 @GetMem:
     99 004041D4 85C0             test eax,eax  
    100 004041D6 7E13             jle $004041eb //eax小于等于0------------------|
    101 004041D8 FF1560A74A00     call dword ptr [$004aa760] //调用SysGetMem   |
    102 004041DE 85C0             test eax,eax //eax=00eb0ec0                  |
    103 004041E0 7402             jz $004041e4  //等于0跳,跳不了了----|          |
    104 004041E2 F3C3             rep ret                           |          |
    105 004041E4 B001             mov al,$01                      <-|          |
    106 004041E6 E945010000       jmp Error                                    |
    107 004041EB 31C0             xor eax,eax //返回就是空了                 <-|
    108 004041ED F3C3             rep ret 
    109 004041EF 90               nop 
    110 -------------------------------------------------------------------
    111 //这个就是实际分配内存的函数,太长了.
    112 //类指针是$004a3b54
    113 SysGetMem:
    114 00402CC8 8D5003           lea edx,[eax+$03//edx=11
    115 00402CCB C1EA03           shr edx,$03 //edx右移3位,edx=1
    116 00402CCE 3D2C0A0000       cmp eax,$00000a2c //eax=8,肯定比$0a2c小
    117 00402CD3 53               push ebx //ebx=$004a3bac,偏移是$58(88),查表 是vmtSelfPtr指向虚方法表的指针 
    118 00402CD4 8A0D51D04A00     mov cl,[$004ad051] //cl=00,因为小头地址
    119 00402CDA 0F8748020000     jnbe $00402f28 //如果eax不小于等于$0a2c,跳不了----------------------------------------
    120 00402CE0 84C9             test cl,cl
    121 00402CE2 0FB682E0D84A00   movzx eax,[edx+$004ad8e0] //eax=0
    122 00402CE9 8D1CC580A04A00   lea ebx,[eax*8+$4aa080]   //ebx=$004aa080
    123 00402CF0 7556             jnz $00402d48 //cl不等于0,跳不了---------------------------------------
    124 00402CF2 8B5304           mov edx,[ebx+$04//edx=$00eb0ce0
    125 00402CF5 8B4208           mov eax,[edx+$08//eax=$00eb0ec0 后面一个Word感觉有猫腻
    126 00402CF8 B9F8FFFFFF       mov ecx,$fffffff8 //ecx=$fffffff8
    127 00402CFD 39DA             cmp edx,ebx //比较ebx和edx,等于0跳
    128 00402CFF 7417             jz $00402d18 //跳不了
    129 00402D01 83420C01         add dword ptr [edx+$0c],$01//将edx+$0c地址上面的数+1,位$0000001D
    130 00402D05 2348FC           and ecx,[eax-$04//ecx与上eax-$4地址上的数(00000001),ecx=0
    131 00402D08 894A08           mov [edx+$08],ecx //edx+$08地址上数为0
    132 00402D0B 8950FC           mov [eax-$04],edx //$00EB0EBC->$00eb0ce0
    133 00402D0E 7428             jz $00402d38 //ecx=0 所以这里跳走----|
    134 00402D10 C60300           mov byte ptr [ebx],$00             |
    135 00402D13 5B               pop ebx                            |
    136 00402D14 C3               ret                                |
    137 00402D15 90               nop                                |
    138 00402D16 90               nop                                |
    139 00402D17 90               nop                                |
    140 00402D18 8B5310           mov edx,[ebx+$10]                  |
    141 00402D1B 0FB74B02         movzx ecx,[ebx+$02]                |
    142 00402D1F 01C1             add ecx,eax                        |
    143 00402D21 3B430C           cmp eax,[ebx+$0c]                  |
    144 00402D24 7776             jnbe $00402d9c                     |
    145 00402D26 83420C01         add dword ptr [edx+$0c],$01        |
    146 00402D2A 894B08           mov [ebx+$08],ecx                  |
    147 00402D2D C60300           mov byte ptr [ebx],$00             |
    148 00402D30 8950FC           mov [eax-$04],edx                  |
    149 00402D33 5B               pop ebx                            |
    150 00402D34 C3               ret                                |
    151 00402D35 90               nop                                | 
    152 00402D36 90               nop                                |
    153 00402D37 90               nop                                |
    154 00402D38 8B4A04           mov ecx,[edx+$04//ecx=$004aa080<-|  
    155 00402D3B 895914           mov [ecx+$14],ebx //[$004aa094]->$004aa080
    156 00402D3E 894B04           mov [ebx+$04],ecx //[$004aa084]->$004aa080 这一块内存都成了它了
    157 00402D41 C60300           mov byte ptr [ebx],$00 //这个地址本来就是0
    158 00402D44 5B               pop ebx //弹出对象指针.
    159 00402D45 C3               ret //返回了,剩下的代码就省略了
    160 ---------------------------------------------------------------------
    161 TObject.InitInstance:
    162 Pascal版本:
    163 class function TObject.InitInstance(Instance: Pointer): TObject;
    164 var
    165   IntfTable: PInterfaceTable;
    166   ClassPtr: TClass;
    167   I: Integer;
    168 begin
    169  //1 首先是将分配对象空间,清0
    170   FillChar(Instance^, InstanceSize, 0);
    171   //2 将实例与类关联起来
    172   PInteger(Instance)^ := Integer(Self);
    173   //
    174   ClassPtr := Self;
    175   //下面的代码是将实例的接口表指向HuangJacky类以及父类的
    176   //相关结构体的定义:
    177   {
    178     PInterfaceEntry = ^TInterfaceEntry;
    179    TInterfaceEntry = packed record
    180      IID: TGUID;
    181      VTable: Pointer;//VMT?
    182      IOffset: Integer;
    183      ImplGetter: Integer;
    184    end;
    185  
    186    PInterfaceTable = ^TInterfaceTable;
    187    TInterfaceTable = packed record
    188      EntryCount: Integer;
    189      Entries: array[0..9999] of TInterfaceEntry;
    190    end;
    191  }
    192   while ClassPtr <> nil do
    193   begin
    194     IntfTable := ClassPtr.GetInterfaceTable;
    195     if IntfTable <> nil then
    196       for I := 0 to IntfTable.EntryCount-1 do
    197      with IntfTable.Entries[I] do
    198      begin
    199        if VTable <> nil then
    200          PInteger(@PAnsiChar(Instance)[IOffset])^ := Integer(VTable);
    201      end;
    202     ClassPtr := ClassPtr.ClassParent;
    203   end;
    204   Result := Instance;
    205 end;
    206 理解了我们来看
    207 汇编版本:
    208 00404DB4 53               push ebx //$004A3BAC,vmtSelfPtr
    209 00404DB5 56               push esi
    210 00404DB6 57               push edi //这3个push都是为了保护现场
    211 00404DB7 89C3             mov ebx,eax //ebx=eax=$004a3bac
    212 00404DB9 89D7             mov edi,edx //edi=edx=$00eb0ec0
    213 00404DBB AB               stosd //将eax移到edi中去,一次移动一个DWORD,隐含add edi,4
    214 00404DBC 8B4BCC           mov ecx,[ebx-$34//ecx=8,InstanceSize.
    215 00404DBF 31C0             xor eax,eax //清空eax,eax=0
    216 00404DC1 51               push ecx //
    217 00404DC2 C1E902           shr ecx,$02 //右移,ecx=2,因为一次移动一个DWORD(4个字节)
    218 00404DC5 49               dec ecx //ecx是循环次数
    219 00404DC6 F3AB             rep stosd //这样[EDI]=0
    220 00404DC8 59               pop ecx
    221 00404DC9 83E103           and ecx,$03 //ecx=0,8与3(100 and 011)
    222 00404DCC F3AA             rep stosb //一次移动一个字节,但是ecx=0,所以不移动 
    223 00404DCE 89D0             mov eax,edx //从前面我们看到edx是@GetMem返回分配了空间的地址,eax=edx=$00eb0ec0,已经初始化0了
    224 00404DD0 89E2             mov edx,esp //edx=esp=栈顶
    225 00404DD2 8B4BAC        |->mov ecx,[ebx-$54//ecx=0 vmtIntfTable= -84;获得类的IntfTable,THuangJacky没有
    226 00404DD5 85C9          |  test ecx,ecx
    227 00404DD7 7401          |  jz $00404dda //---|
    228 00404DD9 51            |  push ecx            |
    229 00404DDA 8B5BD0        |  mov ebx,[ebx-$30<-|ebx=$0040143c//vmtParent= -48;父类,现在是TObject,它也没有有IntfTable
    230 00404DDD 85DB          |  test ebx,ebx
    231 00404DDF 7404          |  jz $00404de5 //      不跳           --|
    232 00404DE1 8B1B          |  mov ebx,[ebx] //ebx=$00401494       |
    233 00404DE3 EBED          |--jmp $00404dd2 //while循环 跳上去      |
    234 00404DE5 39D4             cmp esp,edx //检测中间是否有压栈     <-|
    235 00404DE7 741D             jz $00404e06 //没有,跳--------------------------------------------------------------------------|
    236 00404DE9 5B               pop ebx //这里主要是While循环中那个If的操作                                                       |
    237 00404DEA 8B0B             mov ecx,[ebx]                                                                                  |
    238 00404DEC 83C304           add ebx,$04                                                                                    |
    239 00404DEF 8B7310           mov esi,[ebx+$10]                                                                              |
    240 00404DF2 85F6             test esi,esi                                                                                   |
    241 00404DF4 7406             jz $00404dfc                                                                                   |
    242 00404DF6 8B7B14           mov edi,[ebx+$14]                                                                              |
    243 00404DF9 893407           mov [edi+eax],esi                                                                              |
    244 00404DFC 83C31C           add ebx,$1c                                                                                    |
    245 00404DFF 49               dec ecx                                                                                        |
    246 00404E00 75ED             jnz $00404def                                                                                  |
    247 00404E02 39D4             cmp esp,edx                                                                                    |
    248 00404E04 75E3             jnz $00404de9                                                                                  |
    249 00404E06 5F               pop edi                                                                                    <---|
    250 00404E07 5E               pop esi
    251 00404E08 5B               pop ebx //还原现场
    252 00404E09 C3               ret 
    253 00404E0A 8BC0             mov eax,eax
    254 -----------------------------------------------------
    255 内存块就变成一个对象了,我们看看内存有什么变化了.
    256 00EB0EC0 AC 3B 4A 00 00 00 00 00
    257 我们可以看到一个DWORD指向了我们THuangJacky类的地址
    258 -----------------------------------------------------
    259 构造已接近尾声,看最后一个操作
    260 @AfterConstruction:
    261 0040531C 55               push ebp
    262 0040531D 8BEC             mov ebp,esp
    263 0040531F 51               push ecx
    264 00405320 53               push ebx
    265 00405321 56               push esi
    266 00405322 57               push edi //压栈
    267 00405323 8945FC           mov [ebp-$04],eax
    268 00405326 33D2             xor edx,edx
    269 00405328 55               push ebp
    270 00405329 684B534000       push $0040534b
    271 0040532E 64FF32           push dword ptr fs:[edx]
    272 00405331 648922           mov fs:[edx],esp //这两句代表进入Try了
    273 00405334 8B45FC           mov eax,[ebp-$04]
    274 00405337 8B10             mov edx,[eax]
    275 00405339 FF52E4           call dword ptr [edx-$1c] //调用THuangJacky.AfterConstructor,vmtAfterConstruction=24,我们没有覆盖这个函数,所以直接返回
    276 0040533C 8B45FC           mov eax,[ebp-$04]
    277 0040533F 648F0500000000   pop dword ptr fs:[$00000000]
    278 00405346 83C408           add esp,$08
    279 00405349 EB19             jmp $00405364 //没有异常直接跳过-------------------------------------|
    280 0040534B E984010000       jmp @HandleAnyException//这里是异常处理                              |
    281 00405350 B201             mov dl,$01                                                           |
    282 00405352 8B45FC           mov eax,[ebp-$04]                                                    |
    283 00405355 E812000000       call @BeforeDestruction                                              |
    284 0040535A E879050000       call @RaiseAgain                                                     |
    285 0040535F E8C8050000       call @DoneExcept                                                     |
    286 00405364 5F               pop edi                                                            <-|
    287 00405365 5E               pop esi
    288 00405366 5B               pop ebx
    289 00405367 59               pop ecx
    290 00405368 5D               pop ebp
    291 00405369 C3               ret 
    292 0040536A 8BC0             mov eax,eax
    293 ------------------------------------------------------------------
    294 TObject.AfterConstruction:
    295 00405088 C3               ret 
    296 00405089 8D4000           lea eax,[eax+$00]
    297 ------------------------------------------------------------------
    298 整个过程就明了了
    299 THuangJacky.Create
    300   -->TObject.Create
    301      -->@ClassCreate       -->@AfterConstruction
    302         -->TObject.NewInstance
    303           -->TObject.InstanceSize  --> @GetMem -->  TObject.InitInstance
    304 
    305
    排版效果不是很好大家见谅.
    今天就说到这里,关于SEH的知识,我以前博客写过一篇,只是博客挂了,我一会转到CnBlogs来.
    谢谢.
  • 相关阅读:
    Android开发过程中遇到的问题集合(—)
    Android各种屏幕分辨率(VGA、HVGA、QVGA、WQVGA、WVGA、FWVGA) 具体解释
    Android中Context具体解释 ---- 你所不知道的Context
    HttpClient
    StretchDIBits使用方法
    int a[3];中a+1与&amp;a+1差别 -- C
    把质量控制工作往前推进(1)——安装sonarqube
    24点经典算法
    linux服务之udevd
    java实现第六届蓝桥杯星系炸弹
  • 原文地址:https://www.cnblogs.com/huangjacky/p/1618495.html
Copyright © 2020-2023  润新知