在具有多个接口的对象中实现 QueryInterface() 时,为什么我需要显式向上转换()

2021-12-22 00:00:00 multiple-inheritance com visual-c++ c++

假设我有一个类实现了两个或多个 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() 将通过 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 的虚拟表:

[0] fa0
[1] fa1

B 的 vtable:

vtable for B:

[0] fa0
[1] fa1
[2] fb0
[3] fb1

请注意,如果您有 B 虚表,并且将其视为 A 虚表,它就可以正常工作 - 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.

这是一个使用多重继承的例子(使用上面的 AB 的定义)(注意:只是一个例子 - 实现可能会有所不同):

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 的虚拟表:

@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 虚拟表视为 C会突然调用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 指针,它有一个 vtable,看起来像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.

相关文章