如何通过COM接口得到实现该接口的对象实例

哥爱丞

哥爱丞

2016-02-19 14:17

关注图老师设计创意栏目可以让大家能更好的了解电脑,知道有关于电脑的更多有趣教程,今天给大家分享如何通过COM接口得到实现该接口的对象实例教程,希望对大家能有一点小小的帮助。
如何通过COM接口得到实现该接口的对象实例问题由来我的程序为一个基于COM的插件结构,框架需要向插件传递一个IResource接口。IResource
  需要根据不同的插件传递不同的内容。
  接口定义
  IResource = Interface(IDispatch)
    Function GetPath: String; safecall;
  End;
  实现类
  TResource = TClass(TAutoObject, IResource)
  protected
    Function GetPath: String; SafeCall;
  Public
    Path: String;
  End;
  
  Function GetPath: String;
  Begin
    Result:= Path;
  End;
  
  调用部分:
  Var
    Resource: IResource;
    ResourceObj: TResource;
  Begin
    Resource:= CreateComObject(CLASS_Resource) As IResource;
    //想通过强制转换得到TResource;结果失败了:(
    ResourceObj:= TResource(Resource);
    ResourceObj.Path:= '这里设置不同的值';
  End;
  
  请问:
      如何通过IResource得到TResource,从而达到设置PATH值的目的?
  
  目前我采用的方案是再定义一个ISetValue的接口修改里面的PATH属性,感觉用起来比较
  麻烦。 问题的延伸 如果从解决问题出发,通过定义配置接口,如:
  IObjRef = Interface
    function GetObjRef: TObject; safecall;
  end;
  这样得到对象,再对PATH赋值,这样做在没有破坏COM的封装,实现起来也比较清晰。问题至此基本解决。
  
  但本着从分析DELPHI对象与接口之间的关系的出发点,我们还是继续标题中提出的问题:
  
  如何通过COM接口得到实现该接口的对象实例 ? SAVETIME的线索 http://www.delphibbs.com/delphibbs/dispq.asp?lid=2433841  
  SAVETIME的这篇文章中提到了关于DELPHI中对象与接口之间在编译器实现的内存空间情况:
  ----------------|-----------------|----------|--------------|-----------------
   对象/接口指针   | 对象内存空间    |          | 虚方法表     |
   ----------------|-----------------|----------|--------------|-----------------
   MyObject    -  | VMTptr        00|---------| VirtA      00|
                   | FRefCount     04|          | VirtB      04|
   MyIntf      -  | IInterface    08|----|          
                   | FFieldA       0C|    |           | IInterface    跳转表   |
                   | FFieldB       10|    |--------- | addr of QueryInterface |
   MyIntfB     -  | IIntfB        14|---------|      | addr of _AddRef        |
   MyIntfA     -  | IIntfA        18|--|      |      | addr of _Release       |
                                        |      |
                                        |      |      | IIntfB        跳转表   |
                                        |      |---- | addr of ProcB          |
                                        |             | addr of VirtB          |
                                        |
                                        |             | IIntfA        跳转表   |
                                        |----------- | addr of ProcA          |
                                                      | addr of VirtA          |
   ------------------------------------------------------------------------------
  一个对象在调用类的成员函数的时候,比如执行 MyObject.ProcA,会隐含传递一个 Self 指针给这个成员函数:MyObject.ProcA(Self)。Self 就是对象数据空间的地址。那么编译器如何知道 Self 指针?原来对象指针 MyObject 指向的地址就是 Self,编译器直接取出 MyObject^ 就可以作为 Self。
  
  在以接口的方式调用成员函数的时候,比如 MyIntfA.ProcA,这时编译器不知道 MyIntfA 到底指向哪种类型(class)的对象,无法知道 MyIntfA 与 Self 之间的距离(实际上,在上面的例子中 Delphi 编译器知道 MyIntfA 与 Self 之间的距离,只是为了与 COM 的二进制格式兼容,使其它语言也能够使用接口指针调用接口成员函数,必须使用后期的 Self 指针修正),编译器直接把 MyIntfA 指向的地址设置为 Self。从上图可以看到,MyIntfA 指向 MyObject 对象空间中 $18 偏移地址。这时的 Self 指针当然是错误的,编译器不能直接调用 TMyObject.ProcA,而是调用 IIntfA 的“接口跳转表”中的 ProcA。“接口跳转表”中的 ProcA 的内容就是对 Self 指针进行修正(Self - $18),然后再调用 TMyObject.ProcA,这时就是正确调用对象的成员函数了。由于每个类实现接口的顺序不一定相同,因此对于相同的接口在不同的类中实现,就有不同的接口跳转表(当然,可能编辑器能够聪明地检查到一些类的“接口跳转表”偏移量相同,也可以共享使用)。
  
  通过这里得到了解决问题的关键,如果能得到接口的偏移地址,那么就可以得到对象实例
  
  呵呵~~看到曙光了,加油! 寻找偏移地址 众所周知,所有的DELPHI对象都是从TObject继承下来的,而创建对象也是通过
  class function TObject.InitInstance(Instance: Pointer): TObject;
  来分配内存空间的,仔细分析这段代码。
  class function TObject.InitInstance(Instance: Pointer): TObject;
  {$IFDEF PUREPASCAL}
  var
    IntfTable: PInterfaceTable;
    ClassPtr: TClass;
    I: Integer;
  begin
    FillChar(Instance^, InstanceSize, 0);
    PInteger(Instance)^ := Integer(Self);
    ClassPtr := Self;
    while ClassPtr nil do
    begin
      IntfTable := ClassPtr.GetInterfaceTable;
      if IntfTable nil then
        for I := 0 to IntfTable.EntryCount-1 do
    with IntfTable.Entries[I] do
    begin
      if VTable nil then
        //就是它了IOffset,它就是接口的偏移地址
        PInteger(@PChar(Instance)[IOffset])^ := Integer(VTable);
    end;
      ClassPtr := ClassPtr.ClassParent;
    end;
    Result := Instance;
  end;
  
  找到了IOffset,在跟踪发现它属于 接口标识的接口项(PInterfaceEntry)
    PInterfaceEntry = ^TInterfaceEntry;
    TInterfaceEntry = packed record
      IID: TGUID;
      VTable: Pointer;
      IOffset: Integer;
      ImplGetter: Integer;
    end;
  
  问题出来了,得到PInterfaceEntry 就得到了一切 轻松得到PInterfaceEntry Var
    eResourceObj: TResource;
    eEntry: PInterfaceEntry;
    eAutoObjFactory: TAutoObjectFactory;
  Begin
    eResource:= CreateComObject(CLASS_Resource) as IResource;
    //得到类工厂
    eAutoObjFactory:= TAutoObjectFactory(ComClassManager.GetFactoryFromClassID(CLASS_Resource));
    //得到接口标识的接口项
    eEntry:= eAutoObjFactory.DispIntfEntry;
    //IOffset为接口的偏移地址,eResource减去IOffset所得到的地址就是对象实例
    eResourceObj:= TResource(Integer(eResource)-eEntry.IOffset);
    eResourceObj.Path:= '这里设置不同的值'';
  End;   结论 费劲周折得来的结果,可能对整个问题并没有太多的意义
  但是,过程确实非常有意义,通过这个过程让我对DELPHI对象和接口的实质有了更深层次的了解。  
展开更多 50%)
分享

猜你喜欢

如何通过COM接口得到实现该接口的对象实例

编程语言 网络编程
如何通过COM接口得到实现该接口的对象实例

Microsoft Agent的COM接口编程

编程语言 网络编程
Microsoft Agent的COM接口编程

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

Com接口入门细详(一)

编程语言 网络编程
Com接口入门细详(一)

Com接口入门细详(二)

编程语言 网络编程
Com接口入门细详(二)

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

如何实现禁止USB接口直接运行

电脑入门
如何实现禁止USB接口直接运行

实现VC与Matcom的接口步骤

编程语言 网络编程
实现VC与Matcom的接口步骤

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

Delphi中使用动态SQL的几个问题

Delphi中使用动态SQL的几个问题

Delphi中MIDAS前台程序如何连到后台(MIDAS之五)

Delphi中MIDAS前台程序如何连到后台(MIDAS之五)
下拉加载更多内容 ↓