在具有多个接口()的对象中实现QueryInterface()时,为什么我需要明确的向上转换?
假设我有一个实现两个或多个COM接口的类:
class CMyClass : public IInterface1, public IInterface2 { };
几乎我看到的每个文档都build议,当我为IUnknown实现QueryInterface()时,我显式地将此指针上传到其中一个接口:
if( iid == __uuidof( IUnknown ) ) { *ppv = static_cast<IInterface1>( this ); //call Addref(), return S_OK }
问题是为什么我不能只复制这个 ?
if( iid == __uuidof( IUnknown ) ) { *ppv = this; //call Addref(), return S_OK }
这些文档通常说,如果我做了后者,我将违反在同一对象上对QueryInterface()的任何调用必须返回完全相同的值的要求。
我不太明白。 他们的意思是,如果我QI()为IInterface2并调用QueryInterface()通过该指针C ++将通过这个略有不同,如果我QI()IInterface2因为C ++将每次使这一点的子对象?
问题是, *ppv
通常是一个void*
– 直接赋值给它只会取现有的this
指针并给*ppv
赋值(因为所有指针都可以被赋值为void*
)。
这不是单inheritance的问题,因为对于所有类,基指针总是相同的(因为vtable只是为派生类扩展)。
但是,对于多重inheritance,实际上最终会有多个基本指针,具体取决于您正在讨论的类的“视图”。 原因是多inheritance你不能只扩展vtable – 你需要多个vtables,这取决于你正在谈论的分支。
所以你需要转换this
指针来确保编译器把正确的基址指针(正确的vtable)放到*ppv
。
这是一个单inheritance的例子:
class A { virtual void fa0(); virtual void fa1(); int a0; }; class B : public A { virtual void fb0(); virtual void fb1(); int b0; };
A:vtable
[0] fa0 [1] fa1
v的B:
[0] fa0 [1] fa1 [2] fb0 [3] fb1
请注意,如果您拥有B
vtable,并且将它视为A
vtable,那么它就可以工作 – A
的成员偏移正是您所期望的。
下面是一个使用多inheritance的例子(使用上面的A
和B
定义)(注意:只是一个例子 – 实现可能会有所不同):
class C { virtual void fc0(); virtual void fc1(); int c0; }; class D : public B, public C { virtual void fd0(); virtual void fd1(); int d0; };
v的C:
[0] fc0 [1] fc1
v:D:
@A: [0] fa0 [1] fa1 [2] fb0 [3] fb1 [4] fd0 [5] fd1 @C: [0] fc0 [1] fc1 [2] fd0 [3] fd1
而实际的内存布局为D
:
[0] @A vtable [1] a0 [2] b0 [3] @C vtable [4] c0 [5] d0
请注意,如果你把一个D
表作为一个A
,它将起作用(这是巧合 – 你不能依赖它)。 但是,如果在调用c0
(编译器期望在vtable的0号槽中)时将一个D
vtable作为C
,那么您将突然调用a0
!
当你在D
上调用c0
时,编译器所做的事情实际上是传递了一个伪指针, this
指针有一个vtable,它看上去应该是C
。
所以当你在D
上调用一个C
函数时,在调用函数之前,需要调整vtable指向D
对象的中间(在@C
vtable中)。
你正在做COM编程,所以在查看QueryInterface
为什么被实现之前,需要回顾一下你的代码。
-
IInterface1
和IInterface2
来自IUnknown
,我们假设它们都不是另一个的后代。 - 当对象调用
QueryInterface(IID_IUnknown, (void**)&intf)
时,intf
将被声明为IUnknown*
types。 - 有多个“视图”你的对象 – 接口指针 – 和
QueryInterface
可以通过其中任何一个调用。
因为第3点,你的QueryInterface
定义中的值可能会有所不同。 通过IInterface1
指针调用函数, this
将有一个不同的值,如果它通过IInterface2
指针调用。 在任何一种情况下,由于第一点, this
将保存一个types为IUnknown*
的有效指针,所以如果你简单地赋值*ppv = this
,那么从C ++的angular度来看 ,调用者会很高兴。 你将存储一个types为IUnknown*
的值到同一types的variables中(见#2),所以一切正常。
但是, COM比普通的C ++有更强的规则 。 特别是,它要求对象的IUnknown
接口的任何请求必须返回相同的指针,而不pipe该对象的哪个“视图”被用来调用该查询。 因此,你的对象总是把this
分配给*ppv
是不够的。 有时调用者会得到IInterface1
版本,有时他们会得到IInterface2
版本。 一个适当的COM实现需要确保它返回一致的结果。 它通常会有一个if
– else
梯形图检查所有支持的接口,但其中一个条件将检查两个接口,而不是一个,第二个是IUnknown
:
if (iid == IID_IUnknown || iid == IID_IInterface1) { *ppv = static_cast<IInterface1*>(this); } else if (iid == IID_IInterface2) { *ppv = static_cast<IInterface2*>(this); } else { *ppv = NULL; return E_NOINTERFACE; } AddRef(); return S_OK;
只要在对象仍然存在的情况下分组没有改变,那么将IUnknown
检查的接口分组到哪个接口并不重要,但是,如果这个对象仍然存在,你真的不得不为此做出自己的努力。