Delphi - 对象构造浅析

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

 

ExpandedBlockStart.gif 代码
  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来.
谢谢.

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值