为什么在具有多个接口() 的对象中实现 QueryInterface() 时我需要显式向上转换
假设我有一个实现两个或多个 COM 接口的类:
Assume I have a class implementing two or more COM interfaces:
class CMyClass : public IInterface1, public IInterface2 {
};
我看到的几乎所有文档都表明,当我为 IUnknown 实现 QueryInterface() 时,我明确地将 this 指针向上转换为其中一个接口:
Almost every document I saw suggests that when I implement QueryInterface() for IUnknown I explicitly upcast this pointer to one of the interfaces:
if( iid == __uuidof( IUnknown ) ) {
*ppv = static_cast<IInterface1>( this );
//call Addref(), return S_OK
}
问题是为什么我不能直接复制这个?
The question is why can't I just copy this?
if( iid == __uuidof( IUnknown ) ) {
*ppv = this;
//call Addref(), return S_OK
}
文档通常说,如果我这样做,我将违反对同一对象的任何 QueryInterface() 调用必须返回完全相同的值的要求.
The documents usually say that if I do the latter I will violate the requirement that any call to QueryInterface() on the same object must return exactly the same value.
我不太明白.他们的意思是,如果我对 IInterface2 进行 QI() 并通过该指针调用 QueryInterface(),C++ 将传递 this 与如果我对 IInterface2 进行 QI() 略有不同,因为 C++ 每次都会使 this 指向一个子对象?
I don't quite get that. Do they mean that if I QI() for IInterface2 and call QueryInterface() through that pointer C++ will pass this slightly different from if I QI() for IInterface2 because C++ will each time make this point to a subobject?
推荐答案
问题是*ppv
通常是一个void*
――直接赋值this
将简单地采用现有的 this
指针并赋予 *ppv
它的值(因为所有指针都可以转换为 void*
).
The problem is that *ppv
is usually a void*
- directly assigning this
to it will simply take the existing this
pointer and give *ppv
the value of it (since all pointers can be cast to void*
).
这不是单继承的问题,因为对于单继承,所有类的基指针总是相同的(因为 vtable 只是为派生类扩展).
This is not a problem with single inheritance because with single inheritance the base pointer is always the same for all classes (because the vtable is just extended for the derived classes).
但是 - 对于多重继承,您实际上最终会得到多个基指针,具体取决于您正在谈论的类的视图"!这样做的原因是,通过多重继承,您不能只扩展 vtable - 您需要多个 vtable,具体取决于您正在谈论的分支.
However - for multiple inheritance you actually end up with multiple base pointers, depending on which 'view' of the class you're talking about! The reason for this is that with multiple inheritance you can't just extend the vtable - you need multiple vtables depending on which branch you're talking about.
因此您需要强制转换 this
指针以确保编译器将正确的基指针(用于正确的 vtable)放入 *ppv
.
So you need to cast the this
pointer to make sure that the compiler puts the correct base pointer (for the correct vtable) into *ppv
.
这是一个单继承的例子:
Here's an example of single 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:
vtable for A:
[0] fa0
[1] fa1
用于 B 的 vtable:
vtable for B:
[0] fa0
[1] fa1
[2] fb0
[3] fb1
注意,如果你有 B
vtable 并且你把它当作一个 A
vtable 它只是工作 - A
正是您所期望的.
Note that if you have the B
vtable and you treat it like an A
vtable it just works - the offsets for the members of A
are exactly what you would expect.
这是一个使用多重继承的示例(使用上面的 A
和 B
的定义)(注意:只是一个示例 - 实现可能会有所不同):
Here's an example using multiple inheritance (using definitions of A
and B
from above) (note: just an example - implementations may vary):
class C {
virtual void fc0();
virtual void fc1();
int c0;
};
class D : public B, public C {
virtual void fd0();
virtual void fd1();
int d0;
};
用于 C 的 vtable:
vtable for C:
[0] fc0
[1] fc1
用于 D 的 vtable:
vtable for 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
vtable 视为 A
它将起作用(这是巧合 - 您不能依赖它).但是 - 如果您在调用 c0
时将 D
vtable 视为 C
(编译器期望在 vtable 的插槽 0 中),您'会突然调用a0
!
Note that if you treat a D
vtable as an A
it will work (this is coincidence - you can't rely on it). However - if you treat a D
vtable as a C
when you call c0
(which the compiler expects in slot 0 of the vtable) you'll suddenly be calling a0
!
当你在 D
上调用 c0
时,编译器实际上会传递一个假的 this
指针,该指针有一个看起来像C
.
When you call c0
on a D
what the compiler does is it actually passes a fake this
pointer which has a vtable which looks the way it should for a C
.
所以当你在 D
上调用 C
函数时,它需要调整 vtable 以指向 D
对象的中间(在@C
vtable) 在调用函数之前.
So when you call a C
function on D
it needs to adjust the vtable to point to the middle of the D
object (at the @C
vtable) before calling the function.
相关文章