ゼロからのCOM - クラスの作成とその利用

いちごパック > COM/ActiveXの解説 > ゼロからのCOM - クラスの作成とその利用

COMクライアントの流れ

COMをライブラリとして使うソフトウェアをCOMクライアントといいます。 COMクライアントは次の手順でCOMのクラスインスタンスを利用します。
  • 現在のスレッドに対してCOMシステムを初期化処理を行う。
  • COMのクラスインスタンスをCoCreateInstance()により作成する。
  • 必要なCOMのインターフェースを取得する。
  • インターフェースとして提供されたメソッドを呼び出す。
  • 取得済みインターフェースへのポインタをすべてRelease()することにより、クラスインスタンスを解放する。
  • 現在のスレッドに対するCOMシステムの終了処理を行う。
  • サンプルCOMクライアントの内容

    ここではCOMクライアントのサンプルとして、 メディアファイル再生プログラムを作成します。
    .wavや.aviといったメディアファイルの再生は、 DirectShowライブラリ内のCLSID_FilterGraphクラスによって提供されています。 CLSID_FilterGraphクラスはIMediaControlインターフェース、 IMediaEventインターフェースを含む複数のインターフェースを実装しています。 その概略は次のようになります。
    class IMediaControl // IID_IMediaControl
    {
    public:
        virtual HRESULT RenderFile(BSTR strFilename) = 0;
        virtual HRESULT Run() = 0;
        その他のメソッド
    };
    class IMediaEvent // IID_IMediaEvent
    {
    public:
        virtual HRESULT WaitForCompletion(
            long msTimeout,long *pEvCode) = 0;
        その他のメソッド
    };
    class CFilterGraph // CLSID_FilterGraph
        : public IMediaControl
        , public IMediaEvent
    {
        メソッドの実装
    };
    
    サンプルCOMクライアントでは、 CLSID_FilterGraphで識別されるCOMのクラスを作成し、そのメソッドを次の順に呼び出します。
  • RenderFile()によりメディアファイルを読み込む。
  • Run()によりメディアファイルを再生する。
  • WaitForCompletion()により再生終了を待つ。
  • COMの初期化と終了時処理

    COMを使うスレッドは、COMを初期化する必要があります。 また、スレッド終了時にはCOMの終了時処理が必要になります。 これらを行う関数は次の通りです。
    HRESULT CoInitialize(void* pReserved);
    HRESULT CoUninitialize();
    
    CoInitialize()は、スレッドと1対1に対応付けされるApartmentである Single Thread Apartment(STA)を作成するための初期化関数です。 その動作は次のようになります。
    スレッドの状態動作
    未初期化Single Thread Apartment(STA)を作成し、スレッドをSTAに所属させてS_OKを返す。
    STAに所属内部の初期化カウントを増やしてS_FALSEを返す。
    別のApartmentに所属エラーを返す。
    CoInitialize()の引数pReservedにはNULLを与えます。 また、CoInitialize()がS_FALSEを返した場合も処理としては成功であり、 hrを戻り値としてSUCCEEDED(hr)が成功であればCoUninitialize()を呼び出す必要があります。
    このサンプルでは扱いませんが、 STA以外のApartmentを扱う場合にはCoInitializeEx()により初期化します。 Apartmentについては、マルチスレッドの説明の際に詳しく説明します。

    COMクラス(coclass)の確保

    COMのクラスはコクラス(coclass)と呼ばれています。 C++のクラスインスタンスをnewによって確保するのと同じように、 coclassのインスタンスはCoCreateInstnace()関数によって確保します。
    CoCreateInstance()はC++でのファクトリクラスに似た機能を持ち、 クラスのインスタンスを確保してインターフェースへのポインタを返します。
    CoCreateInstance()は次の通りです。
    HRESULT CoCreateInstance(
        REFCLSID rclsid, IUnknown* punkAggOuter,
        DWORD dwCLSCTX, REFIID riid, LPVOID* ppvObj);
    
    rclsidとriidでcoclassとインターフェースの128ビットGUIDを指定し、 確保したいcoclassとそのインターフェースを識別します。 確保したcoclass内インターフェースへのポインタは *ppvObj に返されます。
    dwCLSCTXはクラスの検索範囲を表すフラグをORで指定するもので、例えば次のフラグが利用できます。 通常は CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER を指定しておけば良いでしょう。
    CLSCTX_INPROC_SERVERDLLとして実装されたCOMサーバを検索します。
    CLSCTX_INPROC_HANDLERインターフェース呼び出し時の中間ハンドラとして、カスタムハンドラを利用可能にします。
    CLSCTX_LOCAL_SERVEREXEとして実装されたCOMサーバを検索します。
    CLSCTX_REMOTE_SERVERリモートマシンで起動されたCOMサーバを検索します。
    CLSCTX_INPROC_HANDLERを指定しておかないと、カスタムハンドラを必要とするCOMサーバは動作しなくなります。
    punkAggOuterはAggregationと呼ばれるクラスの継承機能を使ってCOMサーバを実装する際に使用します。 この例のように、COMクライアントとしてcoclassを作成する場合はNULLを与えます。 Aggregationを使ったCOMサーバの例は別途説明します。

    coclassの作成例

    以上を踏まえ、coclassとしてCLSID_FilterGraphを作成し、 IID_IMediaControlで識別されたインターフェースを取得するコードは次のようになります。 説明のため、エラー処理は省略しています。
    IMediaControl*  pIFMediaControl = NULL;
    HRESULT hr;
    hr = CoInitialize(NULL);
    hr = CoCreateInstance(
        CLSID_FilterGraph, NULL,
        CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER,
        IID_IMediaControl, (LPVOID*)&pIFMediaControl);
    

    別インターフェースの取得

    すでに説明した通り、coclassのすべてのインターフェースはQueryInterface()メソッドを備えています。 QueryInterface()メソッドは次の通りです。 coclassオブジェクトのインターフェースであればどのインターフェースであっても、 QueryInterface()メソッドによってその他のインターフェースへのポインタを取得できます。
    HRESULT QueryInterface(REFIID riid, LPVOID* ppv);
    

    QueryInterfaceの使用例

    IID_IMediaEventで識別されたインターフェースを取得するコードは次のようになります。
    IMediaEvent*    pIFMediaEvent = NULL;
    HRESULT hr;
    hr = pIFMediaControl->QueryInterface(
        IID_IMediaEvent, (LPVOID*)&pIFMediaEvent );
    

    文字列のBSTR変換

    このクラスの場合、RenderFile()メソッドは引数としてBSTRを必要とします。 UNICODE文字列であれば、次の例のようにBSTRに変換できます。
    #define PLAYSAMPLEW L"c:\\windows\\media\\chimes.wav"
    BSTR bstr = SysAllocString(PLAYSAMPLEW);
    
    BSTRの利用後は、SysFreeString()を呼び出して解放します。

    CLSID、IIDのリンク

    DirectShowの場合、dshow.hを#includeするとCLSIDやIIDをDEFINE_GUIDしたヘッダファイルが取り込まれます。 したがって、initguid.hを定義すればリンクエラーは起こりません。 また、DirectShowのようにマイクロソフトが配布するライブラリであれば、 initguid.h済みのライブラリを配布していますので、リンク時にこちらを指定してもかまいません。
    DirectShowの場合は、initguid.h済みのライブラリとしてamstrmid.libが配布されています。 今回はプロジェクトの設定により、このファイルをリンクすることにします。

    メソッドの呼び出し

    coclassを作成し、必要なインターフェースへのポインタを取得できたら、 インターフェースのマニュアル等にしたがってメソッドを呼び出します。 DirectShowを用いてファイル再生を行う場合は、次の手順で呼び出すことでファイルを再生できます。 この例ではWindowsのchimes.wavを再生していますが、 DirectShowが対応していれば、動画ファイルなども再生できます。
    HRESULT hr;
    BSTR bstr = NULL;
    long evCode = 0;
    hr = pIFMediaControl->RenderFile( bstr );
    hr = pIFMediaControl->Run();
    hr = pIFMediaEvent->WaitForCompletion( INFINITE, &evCode );
    

    サンプルプログラム

    エラー処理を追加した、ビルド可能なコードをゼロからのCOM - サンプルCOMクライアントにおきましたので、 こちらをご覧ください。

    関連ページ

  • COM/ActiveXの解説ページ 目次
  • 前の項目: COMの目的とその仕組み
  • 次の項目: IUnknownとその実装