Implementing QueryInterface

このドキュメントでは、QueryInterface() の正しい書き方について解説します。

QueryInterface のリファレンス実装

NS_IMETHODIMP
nsMyImplementation::QueryInterface( REFNSIID aIID, void** aInstancePtr )
  {
    NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!");
      // このメソッドの結果を置く場所を用意せずにこのメソッドを呼ぶのは、実行時エラーではなく、論理エラーです。

      // ...しかし非デバッグビルドにおいて、間違ってこのメソッドを呼び出す時は問題にはなりません。
    if ( !aInstancePtr )
      return NS_ERROR_NULL_POINTER;

    nsISupports* foundInterface;

    if ( aIID.Equals(nsCOMTypeInfo<nsIX>::GetIID()) )
      foundInterface = NS_STATIC_CAST(nsIX*, this);
    else if ( aIID.Equals(nsCOMTypeInfo<nsIY>::GetIID()) )
      foundInterface = NS_STATIC_CAST(nsIY*, this);

    // ...必要に応じて複数の場合を書きます...

    else if ( aIID.Equals(nsCOMTypeInfo<nsISupports>::GetIID()) )
      foundInterface = NS_STATIC_CAST(nsISupports*, NS_STATIC_CAST(nsIX*, this));
        //このオブジェクトは複数の |nsISupports| を持っているかもしれません。
        //  そのため、まず特定のベースインタフェースへキャストして、あいまいさを避けます。
    else
      foundInterface = 0;

    nsresult status;
    if ( !foundInterface )
      status = NS_NOINTERFACE;
    else
      {
        NS_ADDREF(foundInterface);
        status = NS_OK;
      }

    *aInstancePtr = foundInterface;
    return status;
  }

どこが良いのでしょうか?

  • これは、分かりやすく、しかも単純です。
  • OK。これには、ひとつ以上の return がありますが、重要な return はこの関数の最後の return です。そして、付加的な return は分かりやすく、関数の頭に単独で存在します。
  • AddRef がひとつだけしかありません。
  • this ではなく、返って来るインタフェースに対して AddRef を実行しており、COM の規約に沿った方法 (特に集約では重要です) で実行しています。
  • kTIID ではなく、nsCOMTypeInfo<T>::GetIID() を使っています。このようにして、グローバルな宣言とグローバルな空間を保存しています。
  • NS_STATIC_CAST 経由で、C++ の static_cast を使っています。static_cast は実際には、望むインタフェースを得られない時に、エラーを検出します。
  • *aInstancePtrの使用の繰り返しやそれに対する代入の繰り返しは、コンパイラの最適化を困難にしますが、これを避けています。
  • エラーを返すときは、結果、つまり *aInstancePtr をクリアしてます。
  • 典型的な code>QueryInterface</code> の実装より少ないコードを生成します。
  • デバッグビルドにおいて、論理エラーをすぐに見つけるために、NS_ASSERTION を使って、間違った入力をテストしています。

いくつかの代案

NS_IMPL_QUERY_INTERFACE[012] マクロ

上記のサンプルは、nsISupports に加えて、二つの XPCOM インタフェースを実装しています。NS_IMPL_QUERY_INTERFACE2 マクロを使って、この関数を書くことができます。(もっともマクロを勧めるのは気が進まないのですけど。) 例えば、

NS_IMPL_QUERY_INTERFACE2(nsMyImplementation, nsIX, nsIY)
                                          // implements |nsMyImplementation::QueryInterface| as above

NS_IMPL_QUERY_INTERFACE1(nsFoo, nsIFoo)   // |nsFoo::QueryInterface| provides |nsIFoo| and |nsISupports|
NS_IMPL_QUERY_INTERFACE0(nsBar)           // |nsBar::QueryInterface| can only provide an |nsISupports|

同様に、実装するインタフェースをひとつだけ追加したい時は、NS_IMPL_QUERY_INTERFACE1 マクロを使うことができます。また、nsISupports だけを実装する時は、NS_IMPL_QUERY_INTERFACE0 マクロを使うことができます。これらのマクロは、NS_IMPL_ISUPPORTS[012] マクロを使った時に実行されます。このマクロは、対応する QueryInterfaceAddRefRelease の実装を提供します。

継承した QueryInterface を呼び出す

時々、多くのインタフェースをサポートする実装に、単にひとつか二つのインタフェースを加えたい場合があるでしょう。そのような場合は、おそらく、関係のある特定の IID をテストした後で、元の実装を呼び出したいでしょう。これにより、コードスペースと複雑さが低減されます。以下のコードでは、異なっている部分が強調されています。

class nsMyImplmentation : public nsBaseImplementation, public nsIX, public nsIY { ... };

NS_IMETHODIMP
nsMyImplementation::QueryInterface( REFNSIID aIID, void** aInstancePtr )
    /*
      (このクラスには) |nsIX| と |nsIY| を追加しました。
      (このクラスの) ベースクラスの |nsBaseImplementation| は残りのすべてを提供します。
    */
  {
    NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!");

    if ( !aInstancePtr )
      return NS_ERROR_NULL_POINTER;

    nsISupports* foundInterface;

    if ( aIID.Equals(nsCOMTypeInfo<nsIX>::GetIID()) )
      foundInterface = NS_STATIC_CAST(nsIX*, this);
    else if ( aIID.Equals(nsCOMTypeInfo<nsIY>::GetIID()) )
      foundInterface = NS_STATIC_CAST(nsIY*, this);
    // 注: |nsISupports| をチェックしないでください。|nsBaseImplementation| がこのクラスのためにそれを行うはずです
    else
      foundInterface = 0;


    nsresult status;
    if ( !foundInterface )
        // OK, インタフェースを見付けることができませんでした。このクラスのベースクラスがやってくれるでしょう。
      status = nsBaseImplementation::QueryInterface(aIID, &foundInterface);
    else
      {
        NS_ADDREF(foundInterface);
        status = NS_OK;
      }

    *aInstancePtr = foundInterface;
    return status;
  }

ベースの実装の QueryInterface が適切なインタフェースを見付けた場合、あなたの QueryInterface では AddRef を呼び出してはいけないことに注意してください。上記のコードでは、このことが考慮されています。

このテクニックは、nsBaseImplementation がすでにそれ自身で使われている完全なクラスであるために、正常に動作します。このテクニックは、複数の完全なクラスから派生した時は、あまり適切ではありません。しかし、もし順番にこだわるのであれば、このテクニックを使うことができます。例えば、

    // ...
    nsresult status;
    if ( !foundInterface )
      {
        // OK, ask |nsBase1Imp| first, because I want _it_ to be the one true |nsISupports|.
        status = nsBase1Imp::QueryInterface(aIID, &foundInterface);

        if ( !foundInterface )
          status = nsBase2Imp::QueryInterface(aIID, &foundInterface);

        if ( !foundInterface )
          status = nsBase3Imp::QueryInterface(aIID, &foundInterface);
      }
    else
      {
        NS_ADDREF(foundInterface);
        status = NS_OK;
      }
    // ...

もし不可能でないとしても、あなたのベースクラスのどれかが本当の集約に加わって、正常に動作させるのは困難でしょう。集約されたオブジェクト上の QueryInterface に対する呼び出しを捕まえることはできないでしょう。それができたとすると、間違ったインタフェースを返すかもしれません。特に集約を避ける、また複雑な階層構造を避けるもう一つの理由があります。

NS_GET_IID マクロ

あなたは、完全な GetIID 表現をタイプする代わりに、NS_GET_IID マクロを使うことができます。 一般的に私は、マクロが異なる状況で異なるテキストへ拡張されるマクロでない限り、認めません。 例えば、異なるプラットフォーム、デバッグ時と非デバッグ時、などです。 そのような場合、マクロなしで済ますことはできません。 他の場合では、マクロはいくらかの人々を助けるかもしれませんが、しばしば他の問題に悪い影響を与えます。 それらは、常にプログラムソースをより脆弱にしてしまいます。 この場合、マクロは便宜のためだけのものであり、私はこれを勧めはしませんが、代案としては示そうと思います。

    // ...
    if ( aIID.Equals(NS_GET_IID(nsIX)) )
      foundInterface = NS_STATIC_CAST(nsIX*, this);
    else if ( aIID.Equals(NS_GET_IID(nsIY)) )
      foundInterface = NS_STATIC_CAST(nsIY*, this);

    // ...as many cases as needed...

    else if ( aIID.Equals(NS_GET_IID(nsISupports)) )
    // ...

謝辞

Heikki ToivonenChris WatersonJohn Bandhauer に感謝します。らが、価値あるフィードバックをしてくれたおかげで、ここに載せた実装を著しく改善することができました。

原文書の情報

  • 著者: Scott Collins
  • 最終更新日: May 8, 2003
  • 著作権: Portions of this content are © 1998–2007 by individual mozilla.org contributors; content available under a Creative Commons license | 詳細
 

ドキュメントのタグと貢献者

タグ: 
 このページの貢献者: teoli, kohei.yoshino
 最終更新者: teoli,