COM - 扩展“接口”?

Boo*_*oon 0 c++ windows com inheritance

我一直在这里查看 C++ 中的 BHO 教程: http://www.codeproject.com/Articles/37044/Writing-a-BHO-in-Plain-C

COM 类 CClassFactory 和 CObjectWithSite 必须实现 IUnknown。CClassfactory 还必须实现 IClassFactory,CObjectWithSite 也必须实现 IObjectWitSite。创建一个 CUnknown 类,以便两者都可以从它继承,而不必自己实现 IUnknown。

CUnknown 声明如下:

template <typename T>
class CUnknown : public T
Run Code Online (Sandbox Code Playgroud)

CClassFactory 声明如下:

class CClassFactory : public CUnknown<IClassFactory>
Run Code Online (Sandbox Code Playgroud)

并声明 CObjectWithSite:

class CObjectWithSite : public CUnknown<IObjectWithSite>
Run Code Online (Sandbox Code Playgroud)

为什么 CUnknown 扩展了类型参数 T?为什么我们必须将其他接口传递给类构造函数?

ace*_*ent 5

首先,您必须了解 COM 是一个二进制标准,它是每个接口指针IUnknown的基础。

接口指针是对象的入口点,这在 COM 中是看不到的。尽管可以使用例如CoCreateInstance或其他一些 COM 方法(CoCreateInstance最终将调用IClassFactory::CreateInstance)创建新对象,但您实际获得的是一个引用计数的、可能是代理的接口指针只有服务器知道实际的对象

在 Visual C++ 中,我认为在其他一些 Windows C++ 编译器中,类是(或者在其他编译器的情况下可以选择)使用 vtable 实现的,vtable 是函数指针的结构,其中每个函数指针都指向一个虚拟方法。接口指针是指向 vtable 指针的指针,或者更确切地说,是指向具有一个字段的结构体的指针,即指向 vtable 的指针,这并非巧合。

Visual C++ class as a struct
----------------------------
struct vtable *
<fields>
Run Code Online (Sandbox Code Playgroud)

当一个类继承多个“不兼容的类”时,您会得到多个虚函数表,其数量与不兼容的数量一样多(让我们将“不兼容的类”定义为“严格来说不是另一个类的超类/子类的类”,即一个类的层次结构分叉或他们没有任何共同点)。

Visual C++ class as a struct
----------------------------
struct vtable1 *
struct vtable2 *
...
struct vtableN *
<fields>
Run Code Online (Sandbox Code Playgroud)

例如,如果继承IUnknownIClassFactory,您将获得 1 个 vtable,因为IClassFactory继承自IUnknown,因此 的前三个条目IUnknown与 的前三个条目共享IClassFactory。事实上,IClassFactory的 vtable 是一个有效的IUnknownvtable。

class CFoo : IClassFactory
--------------------------
struct IClassFactory_vtable * -\
<fields>                       |
                               |
IClassFactory_vtable <---------/   (IUnknown_vtable)
--------------------               -----------------
QueryInterface                     QueryInterface
AddRef                             AddRef
Release                            Release
CreateInstance
LockServer
Run Code Online (Sandbox Code Playgroud)

继承图:

IUnknown
    ^
    |
IClassfactory
    ^
    |
  CFoo
Run Code Online (Sandbox Code Playgroud)

但是如果你继承egIDispatchIConnectionPointContainer,你会得到2个vtable,每个vtable的前三个条目指向相同的IUnknown方法,但它们仍然是两个不同的结构。

class CBar : SomethingElse, IDispatch, IConnectionPointContainer
----------------------------------------------------------------
struct SomethingElse_vtable * -----------------------------> ...
struct IDispatch_vtable * ------------------------\
struct IConnectionPointContainer_vtable * --------|--------\
<fields>                                          |        |
                   /------------------------------/        |
                   |                                       |
IDispatch_vtable <-/    IConnectionPointContainer_vtable <-/   (IUnknown_vtable)
----------------        --------------------------------       -----------------
QueryInterface     =    QueryInterface                         QueryInterface
AddRef             =    AddRef                                 AddRef
Release            =    Release                                Release
GetTypeInfoCount        EnumConnectionPoints
GetTypeInfo             FindConnectionPoint
GetIDsOfNames
Invoke
Run Code Online (Sandbox Code Playgroud)

您可以搜索“钻石问题”来更好地理解这种方法。

SomethingElse
      ^         IUnknown
      |             ^
      |             |
      |       /-----+-----\
      |       |           |
      |  IDispatch     IConnectionPointContainer
      |       ^           ^
      |       |           |
      |       \-----+-----/
      |             |
      \----+-------/
           |
         CBar
Run Code Online (Sandbox Code Playgroud)

但是,每个 vtable 都是有效的IUnknownvtable。因此,当您实现 时QueryInterface,您必须通过转换为接口指针类型之一来选择哪个作为默认vtable,因为在这种情况下是不明确的。IUnknownthisstatic_cast<IUnknown*>(this)

在链接的文章中,CUnknown::QueryInterface始终返回(void*)this,如果您的第一个继承类不是 COM 接口,或者您继承了多个您打算在 中返回的 COM 接口层次结构,则这是错误的QueryInterface。它的作者QueryInterface只知道如何处理继承单个 COM 接口层次结构作为其第一个继承的类。

您必须为继承层次结构中尽可能多的接口提供各种接口 ID。例如,如果您实现IClassFactory2,则QueryInterface可以在请求IID_IUnknownIID_ClassFactory和时返回相同的 vtable IID_ClassFactory2。据我所知,VC++ 中没有足够的自省来从接口指针类型获取所有 IID,尽管有一个扩展可以获取指定的 IID __uuidof,.

因此,您必须提供完整的 IID 集(或要提供给 的接口类型__uuidof)。

例如,ATL 将使用第一个提供的接口指针映射作为IUnknown接口指针。这是一个任意的选择,但却是一个一致的选择,通过始终返回相同的接口指针来确保遵循身份QueryInterface规则IID_IUnknown

概括

最后,为了回答您的具体问题,CUnknown扩展类型T,以便给定接口指针的 vtable 与 的 vtable 共享IUnknown。或者更确切地说,CUnknown<T>最终实现了无聊的IUnknown方法。

如果你碰巧提供了一个不继承自 的类IUnknown,或者更具体地说,它在继承链中没有兼容的虚拟方法,那么你实际上会在类的第一个 vtable 的末尾得到 , ,而不是拥有QueryInterface所有AddRefCOMRelease接口指向这三个方法。


PS:CClassFactory也可以模板化,因此您可以将其与任何 COM 类重用。

当您掌握了 COM 的“基础知识”时,您一定应该看看 ATL,因为它通常在可能的情况下采用模板方法,在不可行的情况下采用宏方法。

不幸的是,如果你不理解COM,你就不会理解ATL。在不了解 COM 的情况下,您可以轻松地执行一些运行良好的操作、一些巧合或意外的操作以及一些不起作用且最终难以调试的操作。ATL 所做的就是让您免于正确实现大量样板文件的麻烦。