COM - 扩展"界面"?

时间:2014-12-03 15:37:56

标签: c++ windows inheritance com

我一直在用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

CClassFactory声明如下:

class CClassFactory : public CUnknown<IClassFactory>

宣布CObjectWithSite:

class CObjectWithSite : public CUnknown<IObjectWithSite>

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

2 个答案:

答案 0 :(得分:4)

首先,您必须了解COM是一个二进制标准,在每个interface pointer的基础上都有IUnknown

接口指针是对象的入口点,您永远不会在COM中看到它。尽管可以用例如新对象创建新对象。 CoCreateInstance或其他一些COM方法(CoCreateInstance最终将调用IClassFactory::CreateInstance),您实际获得的是refcounted, possibly proxied, interface pointerOnly the server knows about the actual object

在Visual C ++中,我认为在其他一些Windows C ++编译器中,使用vtables实现类(或者可选择地,在其他编译器的情况下),vtable是函数指针的结构,其中每个函数指针指向一个虚方法。接口指针是指向vtable指针的指针,或者更确切地说是pointers to a struct with one field, a pointer to a vtable,这并非巧合。

Visual C++ class as a struct
----------------------------
struct vtable *
<fields>

当一个类继承多个“不兼容的类”时,你得到多个vtable,就像不兼容性一样多(让我们将“不兼容的类”定义为“一个不严格是另一个类的超类/子类的classe”,即一个类的层次结构分叉或它们没有任何共同点。)

Visual C++ class as a struct
----------------------------
struct vtable1 *
struct vtable2 *
...
struct vtableN *
<fields>

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

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

继承图:

IUnknown
    ^
    |
IClassfactory
    ^
    |
  CFoo

但如果你继承了IDispatchIConnectionPointContainer,你得到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

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

SomethingElse
      ^         IUnknown
      |             ^
      |             |
      |       /-----+-----\
      |       |           |
      |  IDispatch     IConnectionPointContainer
      |       ^           ^
      |       |           |
      |       \-----+-----/
      |             |
      \----+-------/
           |
         CBar

但是,每个vtable都是有效的IUnknown vtable。因此,当您实施QueryInterface时,必须选择作为默认IUnknown vtable,方法是将this强制转换为其中一个接口指针类型,因为{{ 1}}在这种情况下是不明确的。

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

您必须为继承层次结构中的任意数量的接口提供各种接口ID。例如,如果您实施QueryInterface,那么当您被要求IClassFactory2QueryInterfaceIID_IUnknown时,您的IID_ClassFactory可能会返回相同的vtable。据我所知,VC ++中没有足够的内省来从接口指针类型中获取所有IID,尽管有一个扩展来获取指定的IID,IID_ClassFactory2

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

例如,ATL将使用第一个提供的interface pointer mapping作为__uuidof接口指针。这是一个随意的选择,但是通过始终返回IUnknown的相同接口指针来确保遵循identity QueryInterface rule的一致性选择。

摘要

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

如果碰巧提供的类不是从IUnknown继承的,或者更具体地说,是继承链中没有兼容的虚方法,那么实际上是IUnknown,{{ 1}}和QueryInterface在类的第一个vtable的末尾,而不是让所有COM接口都指向这三个方法。


PS:AddRef也可能是模板化的,所以你可以在任何COM类中重复使用它。

当你掌握COM的“基础知识”时,你一定要看看ATL,因为它通常在可能的情况下采用模板方法,而在没有时采用宏方法。

不幸的是,如果你不理解COM,你就不会理解ATL。你可以很容易地做一些运作良好的事情,一些巧合或意外的事情,一些不起作用的东西,最终难以调试,而不需要知道COM。 ATL所做的是为您免除实施批次样板正确的麻烦。

答案 1 :(得分:3)

恕我直言,这个问题没有实际意义,因为网站上列出的CUknown不正确。具体做法是:

template <typename T> STDMETHODIMP CUnknown<T>::QueryInterface(REFIID riid,void **ppvObject)
{
...
  if(IsEqualIID(riid,supported_iids[n])) {
    (*ppvObject)=(void*)this;
...

此实现不返回指向所需接口的指针,而是指向this开头的指针。如果this实现多个接口(通过C ++继承),则这是不正确的。此外,无论请求什么IID,这个实现总是返回相同的指针,除了实现一个且只有一个接口的普通对象之外,它不可能是正确的。甚至没有像COM聚合和外部未知的那样进入深奥。

我建议您坚持尝试和真实的框架,如ATL,而是使用CComObject。您可以查看SDK atlbase.inl以获得正确的QueryInterface实施(请参阅AtlInternalQueryInterface代码)。