我一直在用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?为什么我们必须将其他接口传递给类构造函数?
答案 0 :(得分:4)
首先,您必须了解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实现类(或者可选择地,在其他编译器的情况下),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>
例如,如果您继承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
vtable,方法是将this
强制转换为其中一个接口指针类型,因为{{ 1}}在这种情况下是不明确的。
在链接的文章中,static_cast<IUnknown*>(this)
始终返回CUnknown::QueryInterface
,如果您的第一个继承的类不是COM接口,或者您继承了多个您想要的COM接口层次结构,那么这是错误的返回(void*)this
。它的作者QueryInterface
只知道如何处理一个继承单个COM接口层次结构的类作为它的第一个继承。
您必须为继承层次结构中的任意数量的接口提供各种接口ID。例如,如果您实施QueryInterface
,那么当您被要求IClassFactory2
,QueryInterface
和IID_IUnknown
时,您的IID_ClassFactory
可能会返回相同的vtable。据我所知,VC ++中没有足够的内省来从接口指针类型中获取所有IID,尽管有一个扩展来获取指定的IID,IID_ClassFactory2
。
因此,您必须提供完整的IID集(或要提供给__uuidof
的接口类型)。
__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
代码)。