Microsoft DirectX 8.0

IUnknown の実装方法

Microsoft® DirectShow® は、Component Object Model (COM) に基づいている。自分でフィルタを書く場合は、フィルタを COM オブジェクトとして実装しなければならない。DirectShow の基底クラスは、このような作業を行うためのフレームワークを提供する。基底クラスを使う必然性はないが、基底クラスを使えば開発プロセスを簡略化することができる。ここでは、COM オブジェクトの内部的な動作と、DirectShow 基底クラスでの COM オブジェクトの実装について説明する。

このトピックは、COM クライアント アプリケーションのプログラミングを理解している、すなわち、IUnknown のメソッドを理解しているユーザーを対象にしているが、COM オブジェクトの開発経験があるユーザーを前提とするものではない。DirectShow は、COM オブジェクトの開発に関する多くを扱う。COM オブジェクトの開発経験があるユーザーは、CUnknown 基底クラスを解説している「CUnknown の使用」セクションを読むべきである。

COM は実装ではなく仕様である。COM は、コンポーネントが従うべき規則を定義する。規則を実際に適用するのは開発者の役目である。DirectShow では、すべてのオブジェクトは C++ の基底クラスから派生する。基底クラス コンストラクタおよびメソッドは、一貫した参照カウントの保持などの COM "ブックキーピング"処理のほとんどを行う。フィルタを基底クラスから派生させることによって、そのクラスの機能を継承することができる。基底クラスを効果的に使用するためには、COM 仕様がどのようにクラスに実装されているかを理解する必要がある。

このトピックは、以下のセクションを含んでいる。

IUnknown の動作

IUnknown のメソッドを使用すると、アプリケーションはコンポーネントのインターフェイスを照会したり、コンポーネントの参照カウントを管理することができる。このセクションは、以下のトピックを含んでいる。

参照カウント

参照カウントは、AddRef メソッドでインクリメントされ Release メソッドでデクリメントされる内部変数である。基底クラスは、参照カウントを管理し、複数のスレッド間で参照カウントへのアクセスを同期化する。

インターフェイスの照会

インターフェイスの照会はわかりやすい。呼び出し元は、インターフェイス識別子 (IID) とポインタのアドレスの 2 つのパラメータを渡す。要求されたインターフェイスをコンポーネントがサポートしている場合、コンポーネントはそのインターフェイスへのポインタを設定し、専用の参照カウントをインクリメントし、S_OK を返す。コンポーネントがそのインターフェイスをサポートしていない場合、コンポーネントは NULL へのポインタを設定し、E_NOINTERFACE を返す。以下の擬似コードは、QueryInterface メソッドの概要を示す。次の節で説明するコンポーネントの集成化では、処理が複雑になっている。

if (IID == IID_IUnknown)
    set pointer to (IUnknown *)this
AddRef
return S_OK

else if (IID == IID_ISomeInterface)
    set pointer to (ISomeInterface *)this
AddRef
return S_OK

else if ... 

else
    set pointer to NULL
    return E_NOINTERFACE

1 つのコンポーネントの QueryInterface メソッドと別のコンポーネントの QueryInterface メソッドの唯一の違いは、それぞれのコンポーネントがテストする IID のリストにある。コンポーネントがサポートするすべてのインターフェイスに対して、コンポーネントはそのインターフェイスの IID のテストを行わなければならない。

集成化と委任

コンポーネントの集成は呼び出し元に対して透過的でなければならない。したがって、集成は、外部コンポーネントの実装に従う集成されたコンポーネントを使用して、1 つの IUnknown インターフェイスを公開しなければならない。そうでないと、呼び出し元は同じ集成内で 2 つの異なる IUnknown インターフェイスを認識することになる。コンポーネントが集成されていない場合、コンポーネントは独自の実装を使用する。

この動作をサポートするためには、コンポーネントは間接的なレベルを追加しなければならない。委任 IUnknown は、作業を適切な場所に委任する。すなわち、インターフェイスが 1 つであれば外部コンポーネントか、またはコンポーネントの内部バージョンに委任する。非委任 IUnknown は、前の節で説明した操作を行う。

委任バージョンはパブリックで、IUnknown の名前をそのまま保持する。非委任バージョンは INonDelegatingUnknown に名前が変更される。これはパブリック インターフェイスではないので、この名前は COM 仕様の一部ではない。

クライアントがコンポーネントのインスタンスを作成するとき、クライアントは IClassFactory::CreateInstance メソッドを呼び出す。パラメータは、集成コンポーネントの IUnknown インターフェイスへのポインタか、または新しいインスタンスが集成されていない場合は NULL である。以下の例に示すように、コンポーネントは使用する IUnknown インターフェイスを示すメンバ変数を格納するためにこのパラメータを使用する。

CMyComponent::CMyComponent(IUnknown *pOuterUnkown)
{
    if (pOuterUnknown == NULL)
        m_pUnknown = (IUnknown *)(INonDelegatingUnknown *)this;
    else
        m_pUnknown = pOuterUnknown;

    [ ... コンストラクタ コード ... ]
}

委任 IUnknown の各メソッドは、次の例に示すように対応する非委任メソッドを呼び出す。

HRESULT QueryInterface(REFIID iid, void **ppv) 
{
    return m_pUnknown->QueryInterface(iid, ppv);
}

委任動作の本質上、委任メソッドはすべてのコンポーネントにおいて同じに見える。非委任バージョンだけが変わる。

CUnknown の使用

DirectShow は CUnknown と呼ばれる基底クラスの IUnknown を実装する。この CUnknown を使用してほかのクラスを派生し、コンポーネント間で変わるメソッドだけをオーバーライドすることができる。DirectShow のほかの基底クラスのほとんどは CUnknown から派生するので、作成するコンポーネントを CUnknown またはほかの基底クラスから直接継承することができる。

このセクションは、以下のトピックを含んでいる。

INonDelegatingUnknown

CUnknownINonDelegatingUnknown を実装する。CUnknown は参照カウントを内部的に管理し、ほとんどの状況で派生クラスは同じ 2 つの参照カウント メソッドを実装する。CUnknown は参照カウントがゼロになるとそれ自体を削除する。その一方では、NonDelegatingQueryInterface をオーバーライドしなければならない。なぜなら、基底クラスのメソッドは IID_IUnknown 以外の IID を受け取ると E_NOINTERFACE を返すからである。派生クラスでは、以下の例に示すようにサポートするインターフェイスの IID をテストする必要がある。

STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    if (riid == IID_ISomeInterface)
    {
    return GetInterface((ISomeInterface*)this, ppv);
    }
    // デフォルト
    return CUnknown::NonDelegatingQueryInterface(riid, ppv);
}

ユーティリティ関数 GetInterface はポインタを設定し、スレッドに対応した方法で参照カウントを実装し、S_OK を返す。デフォルトのケースでは、基底クラス メソッドを呼び出してその結果を返す。別の基底クラスから派生させた場合は、その NonDelegatingQueryInterface メソッドを代わりに呼び出す。これにより、親クラスがサポートするすべてのインターフェイスをサポートすることができる。

IUnknown

上述のように、IUnknown の委任バージョンは、非委任バージョンの正しいインスタンスを起動するだけなのですべてのコンポーネントで同じである。便宜上、ヘッダー ファイル Combase.h は、インライン メソッドとして 3 つの委任メソッドを宣言するマクロ DECLARE_IUNKNOWN を含んでいる。このコードを以下に示す。

STDMETHODIMP QueryInterface(REFIID riid, void **ppv) {      
    return GetOwner()->QueryInterface(riid,ppv);            
};                                                          
STDMETHODIMP_(ULONG) AddRef() {                             
    return GetOwner()->AddRef();                            
};                                                          
STDMETHODIMP_(ULONG) Release() {                            
    return GetOwner()->Release();                           
};

ユーティリティ関数 GetOwner は、このコンポーネントを所有するコンポーネントの IUnknown インターフェイスへのポインタを取得する。集成したコンポーネントの場合、その所有者は外部コンポーネントである。それ以外の場合、コンポーネントはそれ自体を所有する。クラス定義の public セクションに DECLARE_IUNKNOWN マクロをインクルードする。

クラス コンストラクタ

クラスに固有の操作に加えて、クラス コンストラクタは、親クラスのコンストラクタ メソッドを起動しなければならない。以下の例に、一般的なコンストラクタ メソッドを示す。

CMyComponent(TCHAR *tszName, LPUNKNOWN pUnk, HRESULT *phr) 
: CUnknown(tszName, pUnk, phr)
{ 
/* その他の初期化 */ 
};

このメソッドは以下のパラメータを取って CUnknown コンストラクタ メソッドに直接渡す。

要約

以下に、IUnknown と ISomeInterface という名前の仮想のインターフェイスをサポートする派生クラスの例を示す。

class CMyComponent : public CUnknown, public ISomeInterface
{
public:

DECLARE_IUNKNOWN;

STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv)
    {
        if( riid == IID_ISomeInterface )
        {
        return GetInterface((ISomeInterface*)this, ppv);
        }
        return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }

CMyComponent(TCHAR *tszName, LPUNKNOWN pUnk, HRESULT *phr) 
: CUnknown(tszName, pUnk, phr)
    { 
/* その他の初期化 */ 
    };

    // 後で宣言を追加する。
};

この例では、以下のポイントを示している。

フィルタ開発における次のステップでは、アプリケーションを有効にしてコンポーネントの新しいインスタンスを作成する。このためには、DLL に関する知識に加え、クラス ファクトリおよびクラス コンストラクタ メソッドとの関係を理解していることが必要になる。詳細については、「DLL の作成方法」を参照すること。