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

原创 2004年10月15日 19:16:00

如何通过COM接口得到实现该接口的对象实例
                                            -阿鬼(heroyin
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

问题由来

我的程序为一个基于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       <?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />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对象和接口的实质有了更深层次的了解。

 

COM组件的接口和对象

一、 前言 在COM规范中,最基本的两个要素就是对象与接口,因为COM就是由这两者来共同实现的。COM对象在组件中是被封装起来的,客户代码只能通过接口来访问COM对象并享受其服务,由于客户与COM...
  • u010523811
  • u010523811
  • 2017年02月07日 17:21
  • 2145

如何得到RequestDispatcher对象

有三种方法可以得到Request Dispatcher对象。A.javax.servlet. ServletRequest的getRequestDispatcher(String path)方法,其中...
  • pengpenglin
  • pengpenglin
  • 2006年01月19日 20:43
  • 1736

InitializingBean接口

InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候会执行该方法。 总结: 1...
  • yunxizixuan
  • yunxizixuan
  • 2014年03月21日 21:17
  • 417

利用反射获得对象的属性值

命名空间:System.Reflection 程序集:mscorlib(在 mscorlib.dll 中) C#利用反射,遍历获得一个类的所有属性名,以及该类的实例的所有属性的值 总结: 对...
  • qin_zhangyongheng
  • qin_zhangyongheng
  • 2015年04月29日 08:56
  • 1379

COM 接口得到实现该接口的对象实例

参见http://www.wangchao.net.cn/bbsdetail_47176.html 外部调用Vote1.Base :=Base1.DefaultInterface; Base属性设置为...
  • xxfly
  • xxfly
  • 2009年10月10日 17:19
  • 329

com学习(五)——实现多接口

从第五回开始到第七回,咱们用 ATL 写了一个简单的 COM 组件,之所以说简单,是因为在组件中,只实现了一个自定义(custom)的接口 IFun。当然如果想偷懒的话,我们可以把 200 个函数都加...
  • bestone0213
  • bestone0213
  • 2014年07月10日 14:25
  • 520

C# 编写COM接口

1、新建一个类库项目 编写COM接口" alt="" src="http://album.hi.csdn.net/app_uploads/eglic/20081106/131117889.p.JPG...
  • shilang999
  • shilang999
  • 2012年01月08日 22:12
  • 4136

VC 怎样调用COM控件的接口函数

-------------------------------- COM库函数 -------------------------------- 利用COM库函数使用代码组件的方法是本文介绍的三种方法...
  • lltaoyy
  • lltaoyy
  • 2010年11月02日 15:28
  • 5856

com接口的使用

​适用:本文只针对com接口的基本使用方法记录,即获取一个com接口库如何安装及用C++调用其中的接口。不包括com接口的编写。 获取:假设现得到一个com接口库pta.dll。...
  • cxb23
  • cxb23
  • 2012年10月30日 11:06
  • 1112

【COM原理和应用】2、COM对象和接口

1、COM对象 在客户程序与组件交互的过程中,COM组件将以COM对象形式封装的实体提供给客户程序。...
  • shaqoneal
  • shaqoneal
  • 2015年02月04日 17:56
  • 880
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:如何通过COM接口得到实现该接口的对象实例
举报原因:
原因补充:

(最多只允许输入30个字)