14

假设我有一个实现两个或多个 COM 接口的类:

class CMyClass : public IInterface1, public IInterface2 {
};

我看到的几乎所有文档都表明,当我为 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() 调用必须返回完全相同的值的要求。

我不太明白。它们是否意味着如果 IInterface2 的 I QI() 并通过该指针 C++ 调用 QueryInterface() 将传递这个与 IInterface2 的 I QI() 略有不同,因为 C++ 每次都会将此指向子对象?

4

2 回答 2

27

问题是*ppv通常是void*- 直接分配this给它只会获取现有this指针并给出*ppv它的值(因为所有指针都可以转换为void*)。

这不是单继承的问题,因为对于单继承,所有类的基指针总是相同的(因为 vtable 只是为派生类扩展的)。

但是-对于多重继承,您实际上最终会得到多个基指针,具体取决于您正在谈论的类的“视图”!这样做的原因是,通过多重继承,您不能只扩展 vtable - 您需要多个 vtable,具体取决于您正在谈论的分支。

因此,您需要转换this指针以确保编译器将正确的基指针(对于正确的 vtable)放入*ppv.

下面是一个单继承的例子:

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

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

请注意,如果您拥有Bvtable 并且将其视为Avtable,它就可以正常工作 - 成员的偏移量A正是您所期望的。

这是一个使用多重继承的示例(使用上面的定义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;
};

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

请注意,如果您将Dvtable 视为A它会起作用(这是巧合 - 您不能依赖它)。但是 - 如果您在调用时将Dvtable 视为一个(编译器期望在 vtable 的插槽 0 中),您将突然调用!Cc0a0

当您调用c0a时D,编译器实际上会传递一个假this指针,该指针具有一个看起来与C.

因此,当您在其上调用C函数时D,需要在调用函数之前调整 vtable 以指向D对象的中间(在@Cvtable 处)。

于 2009-11-16T15:30:27.567 回答
8

您正在执行 COM 编程,因此在查看为什么QueryInterface以这种方式实现之前,需要回忆一些关于您的代码的事情。

  1. 两者IInterface1和的IInterface2后代IUnknown,让我们假设两者都不是另一个的后代。
  2. 当有东西调用QueryInterface(IID_IUnknown, (void**)&intf)你的对象时,intf将被声明为 type IUnknown*
  3. 您的对象有多个“视图”——接口指针——并且QueryInterface可以通过其中任何一个来调用。

因为第 3 点,thisQueryInterface定义中的值可能会有所不同。IInterface1通过指针调用函数,this其值与通过IInterface2指针调用时不同。在任何一种情况下,由于第 1 点,this它将持有一个有效的类型指针IUnknown*,所以如果你简单地赋值*ppv = this从 C++ 的角度来看,调用者会很高兴。您将类型的值存储IUnknown*到相同类型的变量中(参见第 2 点),所以一切都很好。

但是,COM 比普通的 C++ 有更强的规则。特别是,它要求对IUnknown对象接口的任何请求都必须返回相同的指针,无论该对象的哪个“视图”用于调用查询。因此,您的对象总是只分配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,但你真的必须不遗余力地做到这一点。

于 2010-05-11T17:25:11.457 回答