技术交流,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;