遥かへのスピードランナー

シリコンバレーでAndroidアプリの開発してます。コンピュータービジョン・3D・アルゴリズム界隈にもたまに出現します。

QueryInterfaceとgetInterfaceの違い

Firefoxアドオンでちょっとコアにタブを扱う」を書いてみて、インターフェースを取得するのに、QueryInterface(Ci.nsIXXXXX)としている箇所と、QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIXXXXX)としている箇所の違いが自分でもよく理解できなかったので、いろいろ調べてみた。

まず、MDCには以下の説明がある。
getInterface - MDCより

getInterface is very similar to QueryInterface. The main difference is that interfaces returned from getInterface are not required to provide a way back to the object implementing nsIInterfaceRequestor. The semantics of QueryInterface dictate that given an interface A that you QueryInterface on to get to interface B, you must be able to QueryInterface on B to get back to A. nsIInterfaceRequestor, however, allows you to obtain an interface C from A that may (or most likely) will not have the ability to get back to A.

QueryInterfaceは可逆だけど、getInterfaceは不可逆であるという説明だが、いまいち良く分からない。そこで、真実はソースコードのみ、ということでFirefoxソースコードを調べてみた。
結果として、QueryInterfaceとgetInterfaceはプログラム上役割が決まっているというものではなく、実装に依ってその役割が異なってくる。しかし、ざっとソースを眺めたところのそれぞれのメソッドの役割はおおよそ固定化されており、以下の通りとなっていることが分かった。

  • QueryInterfaceはそのオブジェクト自身に、指定されたインターフェースを持っているかどうかを問い合わせるメソッドであり、返却値はキャストされた自分自身である(ことが多い)。
  • getInterfaceはそのオブジェクト自身に、指定されたインターフェースを持っている必要はなく、何らかの方法で(たとえばメンバ変数を返すなど)指定されたインターフェースを持つオブジェクトを返すメソッドである。従って返却値は自分自身ではないことが多い。

nsDocShellを例にあげて見ていくと、まずQueryInterfaceの実装は、以下の通り。
nsDocShell.cppより

NS_INTERFACE_MAP_BEGIN(nsDocShell)
    NS_INTERFACE_MAP_ENTRY(nsIDocShell)
    NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem)
    NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeNode)
    NS_INTERFACE_MAP_ENTRY(nsIDocShellHistory)
    NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
    NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
    NS_INTERFACE_MAP_ENTRY(nsIScrollable)
    NS_INTERFACE_MAP_ENTRY(nsITextScroll)
    NS_INTERFACE_MAP_ENTRY(nsIDocCharset)
    NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObjectOwner)
    NS_INTERFACE_MAP_ENTRY(nsIRefreshURI)
    NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
    NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
    NS_INTERFACE_MAP_ENTRY(nsIContentViewerContainer)
    NS_INTERFACE_MAP_ENTRY(nsIEditorDocShell)
    NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor)
    NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider)
    NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_END_INHERITING(nsDocLoader)

マクロになっているが、間違いなくQueryInterfaceの実装部分である。
マクロの実態はきわめて単純で、自分自身を指定されたインターフェースに対してキャストしているだけである。

nsISupportsImpl.hより

#define NS_INTERFACE_MAP_ENTRY(_interface)      NS_IMPL_QUERY_BODY(_interface)
//略...
#define NS_IMPL_QUERY_BODY(_interface)                                        \
  if ( aIID.Equals(NS_GET_IID(_interface)) )                                  \
    foundInterface = static_cast<_interface*>(this);                          \
  else

見ての通り、自分自身をキャストしているだけである。

一方で、nsDocShellにおけるgetInterfaceの実装は次のようになっている。
nsDocShell.cppより

NS_IMETHODIMP nsDocShell::GetInterface(const nsIID & aIID, void **aSink)
{
    //略...
    if (aIID.Equals(NS_GET_IID(nsIURIContentListener))) {
        //略...
    }
    else if (aIID.Equals(NS_GET_IID(nsIScriptGlobalObject)) &&
        //略...
    }
    else if ((aIID.Equals(NS_GET_IID(nsIDOMWindowInternal)) ||
              aIID.Equals(NS_GET_IID(nsPIDOMWindow)) ||
              aIID.Equals(NS_GET_IID(nsIDOMWindow))) &&
             NS_SUCCEEDED(EnsureScriptEnvironment())) {
        return mScriptGlobal->QueryInterface(aIID, aSink);
    }
    else if (aIID.Equals(NS_GET_IID(nsIDOMDocument)) &&
             NS_SUCCEEDED(EnsureContentViewer())) {
        mContentViewer->GetDOMDocument((nsIDOMDocument **) aSink);
        return *aSink ? NS_OK : NS_NOINTERFACE;
    }
    //略...
}

これを見ても分かる通り、たとえば、nsIDOMWindowをgetInterfaceで求められた場合、docShellは自分自身を返すのではなく、mScriptGlobalというメンバ変数に対し処理を丸投げしているといえる。

上記のようにQueryInterface可能なインターフェースとgetInterface可能なインターフェースはそれぞれに別々にハードコーディングされている。QueryInterface可能なインターフェースは、XPCOM Reference - XULPlanetなどを参照すればどうにか分からないでもないが、getInterface可能なインターフェースについては、少なくとも自分の調べた限りは参照できるドキュメントが存在しなかった。

ソースコードを参照するしかない、ということであれば何とも遺憾な話である。ドキュメントつくってほしいなぁ(他力本願)。