このドキュメントでは、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]
マクロを使った時に実行されます。このマクロは、対応する QueryInterface
と AddRef
と Release
の実装を提供します。
継承した 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 Toivonen、Chris Waterson、John 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 | 詳細