COM オブジェクトとは

COM オブジェクトとは

コンポーネント オブジェクト モデル (COM) オブジェクトは、基本的に、1 つまたは複数のタスクを実行するためにアプリケーションで使われるブラック ボックスのようなものである。ダイナミックリンク ライブラリ (DLL) として実装するのが最も一般的である。従来の DLL 同様、COM オブジェクトがサポートしているすべての処理は、オブジェクトによって公開されているメソッドをアプリケーションから呼び出すことによって実行できる。アプリケーションが COM オブジェクトとやり取りする方法は、C++ オブジェクトとやり取りする方法と似ている。ただし、これらの間にはいくつかの顕著な相違点がある。

  • COM オブジェクトは、C++ オブジェクトよりもカプセル化の方法が厳密である。オブジェクトを作成しただけでは、公開のメソッドを呼び出せない。COM オブジェクトの公開メソッドは、1 つまたは複数のインターフェイスにグループ化される。メソッドを使うには、オブジェクトを作成して、そのオブジェクトに適したインターフェイスを取得する必要がある。通常、各インターフェイスには関連性のある一連のメソッドが含まれており、これらのメソッドがオブジェクトの特定の機能へのアクセスを提供する。たとえば、IDirect3DCubeTexture9 インターフェイスには、キューブ テクスチャ リソースを操作するメソッド群が含まれている。インターフェイスに属さないメソッドにはアクセスできない。
  • COM オブジェクトと C++ オブジェクトは、作成方法が異なる。COM オブジェクトを作成する方法は複数あるが、すべての方法で COM 固有の技法が用いられる。Microsoft® DirectX® API には、さまざまなヘルパー関数およびメソッドが含まれており、これらを使うことによって、ほとんどの DirectX オブジェクトを簡単に作成できる。
  • オブジェクトの寿命を制御するには、COM 固有の技法を用いる必要がある。
  • COM オブジェクトは明示的にロードする必要がない。通常、COM オブジェクトは DLL に含まれている。しかし、COM オブジェクトを使うために、DLL または静的ライブラリへのリンクを明示的にロードする必要はない。オブジェクトを作成するには、各オブジェクトに割り当てられている一意の登録済み識別子を使う。COM によって正しい DLL が自動的にロードされる。
  • COM はバイナリ仕様である。COM オブジェクトは、さまざまな言語で記述でき、さまざまな言語でアクセスできる。オブジェクトのソース コードについて知る必要はない。たとえば、Microsoft Visual Basic® アプリケーションでは、C++ で記述された COM オブジェクトがごく一般的に使われる。

このドキュメントには、次の項目が含まれている。

オブジェクトとインターフェイス

オブジェクトとインターフェイスの違いを理解するのは重要なことである。オブジェクトは、日常的に、その主となるインターフェイスの名前で呼ばれることもある。しかし、厳密には、この 2 つの用語は意味が異なる。

  • 1 つのオブジェクトが複数のインターフェイスを公開している場合がある。たとえば、すべてのオブジェクトは IUnknown インターフェイスを公開する必要があり、通常、各オブジェクトはこの他にも 1 つ以上のインターフェイスを公開している。 1 つのオブジェクトが多数のインターフェイスを公開することもある。メソッドを使うには、そのメソッドのオブジェクトを作成するだけでなく、正しいインターフェイスを取得する必要がある。
  • 複数のオブジェクトが同じインターフェイスを公開している場合がある。インターフェイスは、特定の一連の操作を実行するメソッドの集合である。インターフェイスの定義によって指定されるのは、そのインターフェイスに属するメソッドの構文と一般的な機能だけである。COM オブジェクトは、特定の一連の操作をサポートする手段として、適切なインターフェイスを公開する。専門性が高く、公開しているオブジェクトが 1 つだけのインターフェイスもある。その他のインターフェイスはさまざまな環境で利用でき、多数のオブジェクトによって公開されている。その極端な例が IUnknown インターフェイスである。 このインターフェイスは、すべての COM オブジェクトが公開しなければならない。
  インターフェイスを公開するオブジェクトは、必ず、そのインターフェイス定義に含まれるすべてのメソッドをサポートしなければならない。言い換えれば、メソッドを呼び出すときに、それが存在するかどうかを心配する必要はない。ただし、各メソッドの実装方法はオブジェクトによって異なる場合がある。たとえば、最終的な結果は同じでも、オブジェクトによって、使うアルゴリズムが異なる場合がある。また、メソッドのすべての機能がサポートされているという保証はない。オブジェクトが一般的なインターフェイスを公開するとき、そのインターフェイスに属するメソッド群のサブセットをサポートするだけでよい場合がある。このとき、サポートされていないメソッドも正常に呼び出すことができるが、E_NOTIMPL が返される。オブジェクトのドキュメントを参照して、各オブジェクトでのインターフェイスの実装状態を確認することを勧める。

COM 標準では、公開済みのインターフェイス定義は変更できないことになっている。たとえば、既存のインターフェイスに新しいメソッドを追加することはできない。インターフェイスを変更するのではなく、新しいインターフェイスを作成する必要がある。インターフェイスに必ず含まなければならないメソッドなどの制約はないが、新しい世代のインターフェイスには、その元になっているインターフェイスのすべてのメソッドと、追加される新しいメソッドを含むのが一般的である。

1 つのインターフェイスに複数の世代が存在することも珍しくない。通常、どの世代でも本質的に全体としては同じタスクを実行するが、細部が異なっている。多くの場合、オブジェクトはすべての世代のインターフェイスを公開する。これによって、古いアプリケーションでは、オブジェクトの古いインターフェイスを継続して使い、新しいアプリケーションでは、新しいインターフェイスの機能を利用することができる。通常、1 つのファミリに属するインターフェイスにはすべて同じ名前が使われ、そこに世代を示す整数が付加される。たとえば、初代のインターフェイスの名前が IMyInterface であったとすると、次の 2 世代の名前は IMyInterface2 および IMyInterface3 となる。DirectX では、通常、各世代のインターフェイスの名前に DirectX のバージョン番号を使っている。

GUID

グローバル一意識別子 (GUID) は、COM プログラミング モデルの主要な部分である。GUID を最も簡単に説明すると、GUID は 128 ビットの構造体である。ただし、GUID を作成するときは、ほかに同じ GUID が存在しないことが保証されなければならない。COM では、次の 2 つの目的のため、GUID を広く使う。

  • 特定の COM オブジェクトを識別するため。COM オブジェクトに割り当てられた GUID をクラス識別子 (CLSID) と呼ぶ。CLSID は、関連付けられている COM オブジェクトのインスタンスを作成するときに使われる。
  • 特定の COM インターフェイスを識別するため。特定の COM インターフェイスに割り当てられた GUID をインターフェイス識別子 (IID) と呼ぶ。IID は、オブジェクトから特定のインターフェイスを要求するときに使われる。公開しているオブジェクトが異なっても、同じインターフェイスであれば、IID も同じである。
  通常、ドキュメントでオブジェクトやインターフェイスを呼ぶときは、わかりやすいように IDirect3D9 などの説明的な名前が使われる。ドキュメントでは、文脈によって何を示しているのかがわかるので、混同される心配はほとんどない。ただし、厳密に言えば、同じ説明的名前を持つオブジェクトまたはインターフェイスがほかに存在しないという保証はない。特定のオブジェクトまたはインターフェイスを間違いなく表すことができる唯一の方法は、GUID によるものである。

GUID は構造体であるが、等価な文字列として表現されることもある。GUID の文字列形式として一般的なのは、32 進数の整数による 8-4-4-4-12 形式である。 つまり、"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} " となり、x は 16 進数の整数に対応する。たとえば、IDirect3D9 インターフェイスの IID を文字列形式で表すと次のようになる。

{1DD9E8DA-1C77-4D40-B0CF-98FEFDFF9512}

実際の GUID は使いづらく、誤入力しやすいので、通常は、GUID と共に等価な名前も用意される。CoCreateInstance などの関数を呼び出すときに、実際の構造体の代わりにこの名前を使える。慣習的な名付け規則として、インターフェイスおよびオブジェクトの説明的な名前の先頭には、それぞれ IID_ および CLSID_ が付けられる。たとえば、IDirect3D9 インターフェイスの IID の名前は IID_IDirect3D8 となる。

HRESULT 値

すべての COM メソッドは、HRESULT と呼ばれる 32 ビットの整数を返す。ほとんどのメソッドでは、HRESULT は基本的に 1 つの構造体を形成し、次の 2 つの独立した情報を含んでいる。

  • メソッドが成功したか、失敗したか。
  • メソッドでサポートされている操作の結果に関する詳細情報。

一部のメソッドは、Winerror.h に標準セットとして定義されている値だけを HRESULT 値として返す。ただし、メソッドは、より専門的な情報を表すカスタム HRESULT 値も返すことができる。通常、これらの値は、メソッドのリファレンス ページに記載されている。

  メソッドのリファレンス ページのリストに記載されている HRESULT 値は、返される可能性がある値のサブセットだけである場合が多い。通常、このリストには、メソッド固有の値と、メソッド固有の意味を持つ標準の値だけが記載されている。ドキュメントに明示的に記載されていなくても、メソッドからは、さまざまな標準の HRESULT 値が返される可能性がある。

HRESULT 値はエラー情報を返すために使われる場合が多いが、これらをエラー コードと見なしてはならない。HRESULT 値には、値詳細情報を含むビットと正否を示すビットが独立して格納されているため、正否を表すコードをいくつでも含めることができる。慣習により、成功コードには S_ というプレフィクスが付けられ、失敗を示すコードには E_ というプレフィクスが付けられる。たとえば、最も一般的に使われるコードに S_OK および E_FAIL がある。それぞれ、単に成功または失敗を示している。

COM メソッドが正否を示すさまざまなコードを返すということは、HRESULT 値のテスト方法に注意が必要であることを意味する。たとえば、ドキュメントに記載されている戻り値を使って、成功の場合は S_OK が返され、失敗の場合は E_FAIL が返されるという仮説に基づいてテストを行ったとする。しかし、メソッドからは、これ以外の失敗または成功を示すコードが返される可能性もある。次のコードは、単純なテストを用いた場合の危険性を示している。hr は、メソッドによって返された HRESULT 値である。


if(hr == E_FAIL)
{
    //Handle the failure
}

else
{
    //Handle the success
}

メソッドが失敗を示すために返す値が E_FAIL だけである限り、このテストは正しく機能する。しかし、メソッドから、E_NOTIMPL や E_INVALIDARG などのエラー値が返される可能性もある。これらの値は成功として解釈され、アプリケーションでエラーが発生する原因となる。

メソッドの呼び出しの結果に関する詳細な情報が必要な場合は、関係のある各 HRESULT 値をテストする必要がある。しかし、必要な情報が、成功か失敗かだけの場合もある。HRESULT 値が成功を示しているのか失敗を示しているのかをテストする強力な方法として、Winerror.h に定義されている次のマクロのいずれかに HRESULT 値を渡すことができる。

  • SUCCEEDED マクロは、成功を示すコードに対しては TRUE を返し、失敗を示すコードに対しては FALSE を返す。
  • FAILED マクロは、失敗を示すコードに対しては TRUE を返し、成功を示すコードに対しては FALSE を返す。

FAILED マクロを使うと、前述のコードを次のように修正できる。


if(FAILED(hr))
{
   //Handle the failure
}

else
{
   //Handle the success
}

このコードは、E_NOTIMPL や E_INVALIDARG を失敗として正しく処理する。

ほとんどの COM メソッドは構造化された HRESULT 値を返すが、一部の少数のメソッドでは HRESULT を使って単純な 1 つの整数を返す。これらのメソッドは、非明示的に常に成功する。この種の HRESULTSUCCEEDED マクロに渡すと、マクロからは必ず TRUE が返される。一般的に使われる例として、IUnknown::Release メソッドがある。このメソッドは、オブジェクトの参照カウントを 1 つずつデクリメントして、現在の参照カウントを返す。参照カウントの説明については、「COM オブジェクトの寿命の管理」を参照すること。

ポインタのアドレス

COM メソッドのリファレンス ページを参照すると、次のような表記が見つかる。


HRESULT CreateDevice(..., IDirect3DDevice9 **ppReturnedDeviceInterface);

COM では、C や C++ でも使われる通常のポインタのほかに、もう 1 つ別のレベルの間接的呼び出しが使われることがある。この第 2 のレベルの間接的呼び出しは、型の宣言の後に続く "**" で示され、通常、変数名には "pp" というプレフィクスが付く。前述の例の ppReturnedDeviceInterface パラメータは、一般的に、IDirect3DDevice9 インターフェイスへのポインタのアドレスと呼ばれる。

C++ と異なり、COM オブジェクトのメソッドに直接アクセスすることはない。その代わり、メソッドを公開しているインターフェイスへのポインタを取得する必要がある。メソッドを呼び出すときは、C++ のメソッドへのポインタを呼び出すときと同様な構文を使う。たとえば、IMyInterface::DoSomething メソッドを呼び出すには、次のような構文を使う。



IMyInterface *pMyIface;
...
pMyIface->DoSomething(...);

間接的呼び出しに第 2 のレベルが必要なのは、インターフェイス ポインタを直接作成しないためである。前述の CreateDevice メソッドのような、さまざまなメソッドの 1 つを呼び出さなければならない。このようなメソッドを使ってインターフェイス ポインタを取得するには、目的のインターフェイスへのポインタを変数として宣言し、その変数のアドレスをメソッドに渡す。つまり、ポインタのアドレスをメソッドに渡す。メソッドが戻ったとき、変数は要求されたインターフェイスを指している。このポインタを使うと、そのインターフェイスの任意のメソッドを呼び出すことができる。インターフェイス ポインタの使い方に関する詳細については、「COM インターフェイスの使い方」 を参照すること。

表示: