ボンジュール・マドモアゼル

本サイトの情報は自己責任にてご利用下さい。

[Inside COM] Inside COM 第8章 コンポーネントの再利用:包含と集約(その2)

 
外側のコンポーネントが非委譲unknownやそのほかの内側のコンポーネントに属するインターフェースを要求すると、外側のコンポーネントの参照カウントがインクリメントされている。これは、クライアントが内側のコンポーネントに属するインタフェースを要求したときは理想的な展開である。
Inside COM 163頁 8.3.5

下線部の "非委譲unknownや" の記述に問題がある。
NondelegatingQueryInterface の実装を確認すると、
HRESULT __stdcall CB::NondelegatingQueryInterface(const IID& iid,
                                                  void** ppv)
{     
    if (iid == IID_IUnknown)
    {
        // !!! CAST IS VERY IMPORTANT !!!
        *ppv = static_cast<INondelegatingUnknown*>(this) ;
    }
    else if (iid == IID_IY)
    {
        *ppv = static_cast<IY*>(this) ;
    }
    else
    {
        *ppv = NULL ;
        return E_NOINTERFACE ;
    }
    reinterpret_cast<IUnknown*>(*ppv)->AddRef() ;
    return S_OK ;
}
Inside COM 158-159頁 8.3.3

下から3行目の AddRef() の呼び出しで、委譲AddRef を呼び出し、外側のコンポーネントの参照カウントをインクリメントしているように見える。
しかし、reinterpret_cast<IUnknown*>(*ppv) のキャストに注目して欲しい。
IID_IUnknown が要求された場合、ppv は、 static_cast<INondelegatingUnknown*>(this) となり、AddRef() 呼び出しは、 reinterpret_cast によって、NondelegatingAddRef() が呼び出されるのではあるまいか。
したがって、「外側のコンポーネントが非委譲unknownやそのほかの内側のコンポーネントに属するインターフェースを要求すると」の非委譲unknown を要求した場合に限っては内側のコンポーネントの参照カウントがインクリメントされるのではないか。一方、要求されたインタフェースが非委譲unknown でなければ、IUnknown を継承しているはずなので、委譲AddRefが呼ばれるだろう。

ところで、クライアントが内側のインタフェースを要求するとき、なぜ、内側の参照カウントではなく外側の参照カウントがインクリメントされることが理想的なのか。
この理由は、内側のコンポーネントの生存期間は、外側のコンポーネントの生存期間に包括されるという寿命管理の要件にある。集約オブジェクトの寿命管理では、外側のコンポーネントがディアクティベートされたら、内側のコンポーネントもディアクティベートされるべきである。

もし、クライアントが、内側のコンポーネントの参照カウントを直接制御するAddRef、Release を呼び出すとしたらどうなるか。そうすると、外側のコンポーネントの関知しないところで、内側のコンポーネントの寿命管理が行われることになる。これではまずい。
したがって、外側のコンポーネントは、内側のコンポーネントについてAddRef、Release による参照/解除の通知を必要とする。

[Inside COM] Inside COM 第8章 コンポーネントの再利用:包含と集約

 
Inside COM AGGRGATECMPNT2 P177-P178

//
// Constructor
//
CB::CB(IUnknown* pUnknownOuter)
: m_cRef(1)
{
    ::InterlockedIncrement(&g_cComponents) ;

    if (pUnknownOuter == NULL)
    {
        trace("Not aggregating; delegate to nondelegating IUnknown.") ;
        m_pUnknownOuter = reinterpret_cast<IUnknown*>
                          (static_cast<INondelegatingUnknown*>
                          (this)) ;
    }
    else
    {
        trace("Aggregating; delegate to outer IUnknown.") ;
        m_pUnknownOuter = pUnknownOuter ;
    }
}


Inside COM AGGRGATECMPNT2 P179-P180

//
// IClassFactory implementation
//
HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter,
                                       const IID& iid,
                                       void** ppv)
{
    // Aggregate only if the requested iid is IID_IUnknown.
    if ((pUnknownOuter != NULL) && (iid != IID_IUnknown))
    {
        return CLASS_E_NOAGGREGATION ;
    }

    // Create component.
    CB* pB = new CB(pUnknownOuter) ; 
    if (pB == NULL)
    {
        return E_OUTOFMEMORY ;
    }

    // Get the requested interface.
    HRESULT hr = pB->NondelegatingQueryInterface(iid, ppv) ;
    pB->NondelegatingRelease() ;
    return hr ;
}
CreateInstance 関数を見ると、コンポーネント CB を集約せずに生成する際、 IID_IUnknown を要求すると、static_cast<INondelegatingUnknown*>(this) が返される。これは非委譲 unknown vptr である。したがって、この返されたポインタを使うと、集約されていないコンポーネントでも非委譲 unknown を直接呼び出せることになる。

しかし、これは本書の157頁 「図8-5 集約されていないコンポーネントでは、委譲unknownから非委譲unknownに呼び出しが委譲される」と話がかみ合わない。問題ないか。

また、非委譲 unknown を直接呼び出す場合と、 pIY->QueryInterface(IID_IUnknown, ppv); のように委譲 unknown を呼び出す場合では、 IID_Unknown を要求した際、返されるポインタは一致するのか。
3.2.1 一定のIUnknownの取得
...コンポーネントのインスタンスにIUnknownを要求すると、どのインタフェースを介して要求しても、同じポインタ値が返される。

に違反してるのではないか。疑問である。

【追記】
疑問が解消した。次のふたつの誤解があった。
  1. QueryInterface に、IID_IUnknown を要求したら、必ず IUnknown の vtbl を指す vptr を返さなければいけない(サンプルでいえば、委譲 unknown を返さなければならない)と誤解した。
  2. 集約オブジェクトとして使用しない場合、 IID_IUnknown インタフェースポインタから、呼ばれる QueryInterface と、IID_IY インタフェースポインタから呼ばれる QueryInterface は、異なる関数である(ここまでは正しい)。そこから、戻り値までも異なるものと誤解した。
結論としては、集約されないコンポーネントとして使用する場合、QueryInterfaceIID_IUnknown を要求すると、一貫して、INondelegatingUnknown の vtbl を指す vptr を返している。この vptr は、static_cast<INondelegatingUnknown*>(this) である。

集約されていないコンポーネント


なお、

つまり、内側のコンポーネントが集約されていない場合は、 委譲(delegating)unknown非委譲unknownに呼び出しを委譲する。
Inside COM 156頁 8.3.5

青字は私の補足。内側のコンポーネントが集約されていない場合、クライアントが、IID_Unknown インタフェースポインタを使う場合には、非委譲unknownが直接呼び出される。