由接口获取对象的代码分析

由接口获取对象的代码分析
       读了《 JCL中由接口获得对象的方法》后,对于文中提到的“在代码执行过程中,接口和对象指针的偏移是硬编码的在汇编中的。转换的过程,就是解析这段汇编的过程”,不是很理解,当然,也代码是如何做到的也不是很清楚,我想很多人也会有同样的问题。在经过请教牛人后,把自己的一些疑难点和理解做一个总结。
JCL的源码如下:

  1. //==================================================================================================
  2. // Interface information
  3. //==================================================================================================
  4. function GetImplementorOfInterface(const I: IInterface): TObject;
  5. { TODO -cDOC : Original code by Hallvard Vassbotn }
  6. { TODO -cTesting : Check the implemetation for any further version of compiler }
  7. const
  8.   AddByte = $04244483// opcode for ADD DWORD PTR [ESP+4], Shortint
  9.   AddLong = $04244481// opcode for ADD DWORD PTR [ESP+4], Longint
  10. type
  11.   PAdjustSelfThunk = ^TAdjustSelfThunk;
  12.   TAdjustSelfThunk = packed record
  13.     case AddInstruction: Longint of
  14.       AddByte: (AdjustmentByte: ShortInt);
  15.       AddLong: (AdjustmentLong: Longint);
  16.   end;
  17.   PInterfaceMT = ^TInterfaceMT;
  18.   TInterfaceMT = packed record
  19.     QueryInterfaceThunk: PAdjustSelfThunk;
  20.   end;
  21.   TInterfaceRef = ^PInterfaceMT;
  22. var
  23.   QueryInterfaceThunk: PAdjustSelfThunk;
  24. begin
  25.   try
  26.     Result := Pointer(I);
  27.     if Assigned(Result) then
  28.     begin
  29.       QueryInterfaceThunk := TInterfaceRef(I)^.QueryInterfaceThunk;
  30.       case QueryInterfaceThunk.AddInstruction of
  31.         AddByte:
  32.           Inc(PChar(Result), QueryInterfaceThunk.AdjustmentByte);
  33.         AddLong:
  34.           Inc(PChar(Result), QueryInterfaceThunk.AdjustmentLong);
  35.       else
  36.         Result := nil;
  37.       end;
  38.     end;
  39.   except
  40.     Result := nil;
  41.   end;
  42. end;

为了理解这段代码,先构造一个简单的例子:

  1. var
  2.   iIntf: IInterface;
  3.   pOut: Pointer;
  4. begin
  5.   iIntf := TInterfacedObject.Create;
  6.   iIntf.QueryInterface( IInterface , pOut);
  7. end;

跟踪调试其汇编代码,顺便学习下汇编:

  1. Unit1.pas.31: iIntf := TInterfacedObject.Create;
  2. 00453355 B201             mov dl,$01
  3. 00453357 A194114000       mov eax,[$00401194]
  4. 0045335C E8A704FBFF       call TObject.Create
  5. 00453361 8BD0             mov edx,eax  //对象的地址保存到edx,为00E64F40
  6. 00453363 85D2             test edx,edx //判断构造是否成功
  7. 00453365 7403             jz $0045336a 
  8. 00453367 83EAF8           sub edx,-$08 //由对象地址获取接口的指针 self - $08
  9. 0045336A 8D45FC           lea eax,[ebp-$04] //获取临时变量iIntf地址,在堆栈中
  10. 0045336D E86E2AFBFF       call @IntfCopy    //赋值
  11. Unit1.pas.32: iIntf.QueryInterface( IInterface , pOut);
  12. 00453372 8D45F8           lea eax,[ebp-$08]  //从堆栈中获取临时变量pOut地址
  13. 00453375 50               push eax           //参数入栈
  14. 00453376 68A4334500       push $004533a4     //
  15. 0045337B 8B45FC           mov eax,[ebp-$04]  
  16. 0045337E 50               push eax           //iIntf入栈
  17. 0045337F 8B00             mov eax,[eax]
  18. 00453381 FF10             call dword ptr [eax] //调用函数

//看编译器是如何找到函数地址的

  1. 00401145 83442404F8       add dword ptr [esp+$04],-$08   //获取对象的地址
  2. 0040114A E9254D0000       jmp TInterfacedObject.QueryInterface //获取对象的方法
  3. 0040114F 83442404F8       add dword ptr [esp+$04],-$08
  4. 00401154 E9434D0000       jmp TInterfacedObject._AddRef
  5. 00401159 83442404F8       add dword ptr [esp+$04],-$08
  6. 0040115E E94D4D0000       jmp TInterfacedObject._Release

现在回头去看代码就比较清楚了:
const
  AddByte = $04244483; // opcode for ADD DWORD PTR [ESP+4], Shortint
  AddLong = $04244481; // opcode for ADD DWORD PTR [ESP+4], Longint
这两个变量实际上对应了
83442404F8       add dword ptr [esp+$04],-$08   //获取对象的地址
汇编码的前四个字节,不过顺序变为由低位到高位了,那么我们要得到的偏移则是最后一个字节F8,也就是-$08
再看转换代码:

  1.     Result := Pointer(I); //接口指针
  2.     if Assigned(Result) then
  3.     begin
  4.       QueryInterfaceThunk := TInterfaceRef(I)^.QueryInterfaceThunk; //实际指向接口所指内存
  5.       case QueryInterfaceThunk.AddInstruction of                   //前四个字节
  6.         AddByte:
  7.           Inc(PChar(Result), QueryInterfaceThunk.AdjustmentByte);  //最后一个字节,偏移接口指针指向对象
  8.         AddLong:
  9.           Inc(PChar(Result), QueryInterfaceThunk.AdjustmentLong);
  10.       else
  11.         Result := nil;
  12.       end;
  13.     end;

遗留的问题:
1,esp+4的内容是什么时候压栈的
2,怎样才能构造出AddLong情况

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值