COM接口定义和标识

《COM原理与应用》笔记

  从技术上讲,接口是包含了一组函数的数据结构,客户程序用一个指向接口数据结构的指针来调用接口成员函数。如图2.2所示,接口指针实际指向另一个指针,第二个指针指向一组函数,称为接口函数表,接口函数表中每一项为4个字节长的函数指针,每个函数指针与对象的具体实现连接起来。通过这种方式,客户程序只要获得接口指针,就可以调用对象的实际功能。

 

  接口函数表通常称为虚函数表(virtual function table,简称vtable),指向vtable的指针为pVtable。
  对于一个接口来说,它的虚函数表vtable是确定的,因此接口的成员函数个数是不变的,而且成员函数的先后顺序也是不变的;对于每个成员函数来说,其参数和返回值也是确定的。在一个接口的定义中,所有这些信息都必须在二进制一级确定,不管什么语言,只要能支持这样的内存结构描述,就可以定义接口。
  用C语言描述字典接口
  struct IDictionaryVtbl;
  struct IDictionary
  {
    IDictionaryVtbl *pVtbl;
  };
  struct IDictionaryVtbl
  {
    BOOL (*Initialize)(IDictionary *this);
    BOOL (*LoadLibrary)(IDictionary *this, String);
    BOOL (*InsertWord)(IDictionary *this, String, String);
    void (*DeleteWord)(IDictionary *this, String);
    BOOL (*LookupWord)(IDictionary *this, String, String *);
    BOOL (*RestoreLibrary)(IDictionary *this, String);
    void (*FreeLibrary)(IDictionary *this);
  };
  (1)每个接口成员函数的第一个参数为指向IDictionary的指针,这是因为接口本身并不独立使用,它必定存在于某个COM对象上,因此,该指针可以提供对象实例的属性信息,在被调用时,接口可以知道是对哪个COM对象在进行操作。所以,该this指针与C++类成员函数定义中隐藏的this指针非常类似。如果我们在一个应用系统中同时用到了两本字典,即存在两个字典对象,不同的字典对象其this指针不同。
  (2)在接口成员函数中,字符串变量必须用Unicode字符指针,COM规范要求使用Unicode字符,而且COM库中提供的COM API函数也使用Unicode字符。所以,如果在组件程序内部用到了ANSI字符的话,应该对两种字符进行转换,操作系统或者C/C++编译库会提供这样的转换函数。
  (3)不仅成员函数的参数类型是确定的,而且应该使用同样的函数调用方式。客户程序在调用成员函数之前,必须先把参数压入栈中,然后再进入成员函数,成员函数依次把参数从栈中取出,在函数返回之前或返回之后,必须恢复栈的当前位置,才能保证程序的正常运行。在Windows平台上有两种调用方式,分别为_cdecl和_stdcall(在有的编译器中称为pascal),采用_cdecl可以实现C语言中用到的函数可变参数的特性(例如printf函数),在这种调用方式下,由调用程序处理栈的恢复。由于大多数语言(除C/C++之外)都使用了_stdcall或pascal,而且大多数的系统API(支持可变参数的函数例外)也都使用这种调用方式,所以,COM规范也采用_stdcall和pascal,并且,所有的COM API函数也使用了_stdcall。当然,这不是绝对的,但必须保证调用方和被调用方使用一致的调用方式,如果接口成员函数使用了_cdecl,则C/C++之外的大多数语言就不能使用这样的接口,所以,除非要使用可变参数特性,否则就使用_stdcall。
  (4)在C语言中,用这种结构只是描述了接口,并没有提供具体的实现,对于客户程序,它只需要这样的描述就可以调用COM对象的接口;而对于组件程序,基于这样的描述必须提供具体的实现过程,也就是说,如果一个COM对象实现了这个接口,则它所提供的接口指针IDictionary所指向的IDictionaryVtbl结构中,每个成员必须是有效的函数指针。
  (5)从C语言的描述中我们可以看出,由于COM接口的这种二进制结构,只要一种编程语言能够支持“structure”或“record”类型,并且这种类型能够包含双重的指向函数指针表的成员,则它就可以支持接口的描述,从而可以用于编写COM组件或者使用COM组件。

---------------------------------------- 分割线 ----------------------------------------

  COM接口也采用了全局唯一标识符,它被称为接口标识符(IID,interface identifier)。例如:
  extern "C" const IID IID_IUnknown =
  { 0x00000000, 0x0000, 0x0000,
  { 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 }};
  客户程序要使用接口,必须知道该接口的IID和接口提供的方法(接口成员函数)。

---------------------------------------- 分割线 ----------------------------------------

COM接口结构中的vtable与class的vtable(类的虚函数表)完全一致,因此,用class描述COM接口是最方便的手段。
  用C++类重新定义IDictionary:
  class IDictionary
  {
    virtual BOOL Initialize() = 0;
    virtual BOOL LoadLibrary(String) = 0;
    virtual BOOL InsertWord(String, String) = 0;
    virtual void DeleteWord(String) = 0;
    virtual BOOL LookupWord(String, String *) = 0;
    virtual BOOL RestoreLibrary(String) = 0;
    virtual void FreeLibrary() = 0;
  };
  class定义中隐藏了虚函数表vtable,每个成员函数隐藏了第一个参数this指针,this指针指向类的实例。图2.3显示了类IDictionary的内存结构:

 

  类IDictionary使用了纯虚函数,因为接口只是一种描述,并不提供具体的实现过程。如果COM对象要实现接口IDictionary,则COM对象必须以某种方式把它自身与类IDictionary联系起来,然后将IDictionary的指针暴露给客户程序。
  当客户程序获得了COM对象的接口指针pIDictionary之后,就可以调用接口的成员函数,例如:
  pIDictionary->LoadLibrary("Eng_Ch.dict");
  如果使用C语言的struct IDictionary,则应该这样:
  pIDictionary->pVtbl->LoadLibrary(pIDictionary, "Eng_Ch.dict");
  由C++语言class的特性可知上述两种调用完全等价。

---------------------------------------- 分割线 ----------------------------------------

  用IDL描述接口IDictionary
  interface IDictionary
  {
    HRESULT Initialize();
    HRESULT LoadLibrary([in]string);
    HRESULT InsertWord([in]string, [in]string);
    HRESULT DeleteWord([in]string);
    HRESULT LookupWord([in]string, [out]string *);
    HRESULT RestoreLibrary([in]string);
    HRESULT FreeLibrary();
  };

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值