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?为什么我们必须将其他接口传递给类构造函数?
首先,您必须了解 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)
例如,如果继承IUnknown和IClassFactory,您将获得 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)
但是如果你继承egIDispatch和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
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_IUnknown、IID_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 所做的就是让您免于正确实现大量样板文件的麻烦。