我一直在这里查看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?为什么我们必须将其他接口传递给类构造函数?

最佳答案

首先,您必须了解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实现的,vtables是函数指针的结构,其中每个函数指针均指向虚拟方法。接口指针是指向vtable指针或pointers to a struct with one field, a pointer to a vtable的指针,这并非巧合。

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


当一个类继承多个“不兼容类”时,您将获得多个不兼容的vtable(让我们将“不兼容类”定义为“严格来说不是另一个类的超类/子类的类”,即一个类的层次结构派生或他们没有任何共同点)。

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强制转换为一种接口指针类型来选择将哪个作为默认this vtable,因为在这种情况下static_cast<IUnknown*>(this)是模棱两可的。

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

您必须为继承层次结构中的多个接口提供各种接口ID。例如,如果实现IClassFactory2,则当要求输入QueryInterfaceIID_IUnknownIID_ClassFactory时,IID_ClassFactory2可能返回相同的vtable。据我所知,尽管存在扩展以获取指定IID __uuidof的扩展,但是VC ++中的自省方式不足以从接口指针类型获取所有IID。

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

例如,ATL将使用第一个提供的interface pointer mapping作为IUnknown接口指针。这是一个任意选择,但是一个一致的选择,可以确保始终跟随QueryInterface返回相同的接口指针,从而确保遵循identity IID_IUnknown rule

摘要

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

如果碰巧提供了一个不继承自IUnknown的类,或更确切地说,提供了一个在继承链中不具有兼容虚拟方法的类,则实际上您会在QueryInterfaceAddRefRelease类的第一个vtable的末尾,而不是让所有COM接口都指向这三个方法。



PS:CClassFactory也可以作为模板,因此您可以在任何COM类中重用它。

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

不幸的是,如果您不了解COM,就不会了解ATL。在不了解COM的情况下,您可以轻松地完成某些工作,某些工作偶然或偶然地工作,而某些工作却无法调试,并且最终难以调试。 ATL所做的是使您免于正确实施大量样板的麻烦。

09-07 00:17