14

2 つ以上の COM インターフェイスを実装するクラスがあるとします。

class CMyClass : public IInterface1, public IInterface2 {
};

私が見たほとんどすべてのドキュメントは、 IUnknown に QueryInterface() を実装するときに、このポインターをインターフェイスの 1 つに明示的にアップキャストすることを示唆しています。

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 に対して QI() を実行し、そのポインターを介して QueryInterface() を呼び出すと、C++ は IInterface2 に対して QI() を実行する場合とは少し異なるこれを渡すということですか?

4

2 に答える 2

27

問題は、*ppv通常 a 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 の vtable:

[0] fa0
[1] fa1

B の vtable:

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

Bvtable があり、それを vtableのように扱う場合、それは機能することに注意してください。Aメンバーのオフセットは、Aまさに期待どおりです。

多重継承を使用した例を次に示します (上記の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;
};

C の vtable:

[0] fc0
[1] fc1

D の vtable:

@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機能することに注意してください (これは偶然です。信頼することはできません)。Dただし、 vtableCを呼び出すときc0(コンパイラは vtable のスロット 0 であると想定)として扱うと、突然a0!を呼び出すことになります。

を呼び出すc0D、コンパイラが行うことは、実際にはthis、 のように見える vtable を持つ偽のポインターを渡しますC

したがって、関数を呼び出すときは、C関数を呼び出す前に、vtable がオブジェクトDの中央 (vtable で) を指すように調整する必要があります。D@C

于 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*type の値を同じ型の変数に格納したので (ポイント 2 を参照)、すべて問題ありません。

ただし、COM には通常の C++ よりも強力な規則がありますIUnknown特に、クエリを呼び出すためにそのオブジェクトのどの「ビュー」が使用されたかに関係なく、オブジェクトのインターフェースに対するすべての要求が同じポインターを返さなければならないことが必要です。thisしたがって、オブジェクトが常に mereを に代入するだけでは十分ではありません*ppv。発信者がバージョンを取得することもあれば、IInterface1バージョンを取得することもありIInterface2ます。適切な COM 実装では、一貫した結果が返されるようにする必要があります。通常、サポートされているすべてifelseインターフェイスをチェックするラダーがありますが、条件の 1 つは、1 つだけではなく 2 つのインターフェイスをチェックします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 に答える