在具有多个接口()的对象中实现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的例子(使用上面的AB定义)(注意:只是一个例子 – 实现可能会有所不同):

 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为什么被实现之前,需要回顾一下你的代码。

  1. IInterface1IInterface2来自IUnknown ,我们假设它们都不是另一个的后代。
  2. 当对象调用QueryInterface(IID_IUnknown, (void**)&intf)时, intf将被声明为IUnknown*types。
  3. 有多个“视图”你的对象 – 接口指针 – 和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实现需要确保它返回一致的结果。 它通常会有一个ifelse梯形图检查所有支持的接口,但其中一个条件将检查两个接口,而不是一个,第二个是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检查的接口分组到哪个接口并不重要,但是,如果这个对象仍然存在,你真的不得不为此做出自己的努力。