我一直在这里查看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 pointer。 Only 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>
例如,如果继承
IUnknown
和IClassFactory
,则将获得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
但是如果您继承例如
IDispatch
和IConnectionPointContainer
,您将获得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
,则当要求输入QueryInterface
,IID_IUnknown
和IID_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
的类,或更确切地说,提供了一个在继承链中不具有兼容虚拟方法的类,则实际上您会在QueryInterface
,AddRef
和Release
类的第一个vtable的末尾,而不是让所有COM接口都指向这三个方法。PS:
CClassFactory
也可以作为模板,因此您可以在任何COM类中重用它。当您掌握COM的“基础知识”时,您一定应该看一下ATL,因为它通常在可能的情况下采用模板方法,而在可能的情况下采用宏方法。
不幸的是,如果您不了解COM,就不会了解ATL。在不了解COM的情况下,您可以轻松地完成某些工作,某些工作偶然或偶然地工作,而某些工作却无法调试,并且最终难以调试。 ATL所做的是使您免于正确实施大量样板的麻烦。