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:
我们可以看到是3个函数地址,这3个函数都是什么呢?
我们看见里面记录了是TInterfacedObject里面实现了IInterface接口里面的3个函数.
第二个THuangJacky的VTable:
这里有了4个函数指针.具体看看都是什么函数:
这4个函数就是IHuangJacky接口要实现的函数.
根据李维大哥的书里面:要在VTable里面来,接口必须要有GUID,额.是么?我们去掉IHuangJacky的GUID看一下:
没有变化,看来和GUID没有关系呀.难道维哥这话还有编译器版本有关.
最后看一下实例所分内存的情况:
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;
改- 88 为 - 84,后看结果:
可以看出这个是一个TInterfaceTable,1是EntryCount,中间16个0是InterfaceEntry的GUID,$004b336c应该是VTable.
改- 84 为 - 80:
0,代表没有不过从名字看vmtAutoTable应该是Automation会用到了,暂时没有测试.
改- 80 为 - 76:
这个也是类THuangJacky的地址
改 –76 为 - 72:
结合名字来看这个应该是是一个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;