Microsoft DirectX 8.0 (C++) |
COM オブジェクトは、基本的に、1 つまたは複数のタスクを実行するためにアプリケーションで使用されるブラック ボックスのようなものである。DLL として実装するのが最も一般的である。従来の DLL 同様、COM オブジェクトがサポートしているすべての処理は、オブジェクトによって公開されているメソッドをアプリケーションから呼び出すことによって実行できる。アプリケーションが COM オブジェクトとやり取りする方法は、C++ オブジェクトとやり取りする方法と似ている。ただし、これらの間にはいくつかの顕著な相違点がある。
オブジェクトとインターフェイスの違いを理解するのは重要なことである。オブジェクトは、日常的に、その主となるインターフェイスの名前で呼ばれることもある。しかし、厳密には、この 2 つの用語は意味が異なる。
注 インターフェイスを公開するオブジェクトは、必ず、そのインターフェイス定義に含まれるすべてのメソッドをサポートしなければならない。言い換えれば、メソッドを呼び出すときに、それが存在するかどうかを心配する必要はない。ただし、各メソッドの実装方法はオブジェクトによって異なる場合がある。たとえば、最終的な結果は同じでも、オブジェクトによって、使用するアルゴリズムが異なる場合がある。また、メソッドのすべての機能がサポートされているという保証はない。オブジェクトが一般的なインターフェイスを公開するとき、そのインターフェイスに属するメソッド群のサブセットをサポートするだけでよい場合がある。このとき、サポートされていないメソッドも正常に呼び出すことができるが、E_NOTIMPL が返される。オブジェクトのドキュメントを参照して、各オブジェクトでのインターフェイスの実装状態を確認することを勧める。
COM 標準では、公開済みのインターフェイス定義は変更できないことになっている。たとえば、既存のインターフェイスに新しいメソッドを追加することはできない。インターフェイスを変更するのではなく、新しいインターフェイスを作成する必要がある。インターフェイスに必ず含まなければならないメソッドなどの制約はないが、新しい世代のインターフェイスには、その元になっているインターフェイスのすべてのメソッドと、追加される新しいメソッドを含むのが一般的である。
1 つのインターフェイスに複数の世代が存在することも珍しくない。通常、どの世代でも本質的に全体としては同じタスクを実行するが、細部が異なっている。多くの場合、オブジェクトはすべての世代のインターフェイスを公開する。これによって、古いアプリケーションでは、オブジェクトの古いインターフェイスを継続して使用し、新しいアプリケーションでは、新しいインターフェイスの機能を利用することができる。通常、1 つのファミリに属するインターフェイスにはすべて同じ名前が使用され、そこに世代を示す整数が付加される。たとえば、初代のインターフェイスの名前が IMyInterface であったとすると、次の 2 世代の名前は IMyInterface2 および IMyInterface3 となる。Microsoft DirectX® では、通常、各世代のインターフェイスの名前に DirectX のバージョン番号を使用している。
グローバル ユニーク識別子 (GUID) は、COM プログラミング モデルの主要な部分の 1 つである。GUID を最も簡単に説明すると、GUID は 128 ビットの構造体である。ただし、GUID を作成するときは、ほかに同じ GUID が存在しないことが保証されなければならない。COM は、主に次の 2 つの目的で GUID を使用する。
注 通常、ドキュメントでオブジェクトやインターフェイスを呼ぶときは、わかりやすいように IDirect3D8 などの説明的な名前が使用される。ドキュメントでは、文脈によって何を示しているのかがわかるので、混同される心配はほとんどない。ただし、厳密に言えば、同じ説明的名前を持つオブジェクトまたはインターフェイスがほかに存在しないという保証はない。特定のオブジェクトまたはインターフェイスを間違いなく表すことができる唯一の方法は、GUID によるものである。
GUID は構造体であるが、等価な文字列として表現されることもある。GUID の文字列形式では、"{VVVVVVVV-WWWW-XXXX-YYYY-ZZZZZZZZZZZZ}" というフォーマットが一般的である。各文字は、16 進表記の整数である。たとえば、IDirect3D8 インターフェイスの IID を文字列形式で表すと次のようになる。
{1DD9E8DA-1C77-4D40-B0CF-98FEFDFF9512}
実際の GUID は使いづらく、誤入力しやすいので、通常は、GUID と共に等価な名前も用意される。CoCreateInstance などの関数を呼び出すときに、実際の構造体の代わりにこの名前を使用できる。慣習的な名付け規則として、インターフェイスおよびオブジェクトの説明的な名前の先頭には、それぞれ IID_ および CLSID_ が付けられる。たとえば、IDirect3D8 インターフェイスの IID は IID_IDirect3D8 となる。
すべての 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) { //失敗時の処理を行う。 } else { //成功時の処理を行う。 }
メソッドが失敗を示すために返す値が E_FAIL だけである限り、このテストは正しく機能する。しかし、メソッドから、E_NOTIMPL や E_INVALIDARG などのエラー値が返される可能性もある。これらの値は成功として解釈され、アプリケーションでエラーが発生する原因となる。
メソッドの呼び出しの結果に関する詳細な情報が必要な場合は、関係のある各 HRESULT 値をテストする必要がある。しかし、必要な情報が、成功か失敗かだけの場合もある。HRESULT 値が成功を示しているのか失敗を示しているのかをテストする強力な方法として、Winerror.h に定義されている次のマクロのいずれかに HRESULT 値を渡すことができる。
FAILED マクロを使うと、前述のコードを次のように修正できる。
if(FAILED(hr)) { //失敗時の処理を行う。 } else { //成功時の処理を行う。 }
このコードは、E_NOTIMPL や E_INVALIDARG を失敗として正しく処理する。
ほとんどの COM メソッドは構造化された HRESULT 値を返すが、一部の少数のメソッドでは HRESULT を使って単純な 1 つの整数を返す。これらのメソッドは、非明示的に常に成功する。この種の HRESULT を SUCCESS マクロに渡すと、マクロからは必ず TRUE が返される。一般的に使用される例として、IUnknown::Release メソッドがある。このメソッドは、オブジェクトの reference count を 1 ずつデクリメントして、現在の参照カウントを返す。参照カウントの説明については、「COM オブジェクトの寿命の管理」を参照すること。
COM メソッドのリファレンス ページを参照すると、次のような表記が見つかる。
HRESULT CreateDevice( ..., IDirect3DDevice8** ppReturnedDeviceInterface );
COM では、C や C++ でも使用される通常のポインタのほかに、もう 1 つ別のレベルの間接的呼び出しが使用されることがある。この第 2 のレベルの間接的呼び出しは、タイプの宣言の後に続く "**" で示され、通常、変数名には "pp" というプレフィクスが付く。前述の例の ppReturnedDeviceInterface パラメータは、一般的に、IDirect3DDevice8 インターフェイスへの「ポインタのアドレス」と呼ばれる。
C++ と異なり、COM オブジェクトのメソッドに直接アクセスすることはない。その代わり、メソッドを公開しているインターフェイスへのポインタを取得する必要がある。メソッドを呼び出すときは、C++ のメソッドへのポインタを呼び出すときと同様な構文を使用する。たとえば、IMyInterface::DoSomething メソッドを呼び出すには、次のような構文の使用する。
IMyInterface *pMyIface; ... pMyIface->DoSomething(...);
間接的呼び出しに第 2 のレベルが必要なのは、インターフェイス ポインタを直接作成しないためである。前述の CreateDevice メソッドのような、さまざまなメソッドの 1 つを呼び出さなければならない。このようなメソッドを使ってインターフェイス ポインタを取得するには、目的のインターフェイスへのポインタを変数として宣言し、その変数のアドレスをメソッドに渡す。つまり、ポインタのアドレスをメソッドに渡す。メソッドが戻ったとき、変数は要求されたインターフェイスを指している。このポインタを使用すると、そのインターフェイスの任意のメソッドを呼び出すことができる。インターフェイス ポインタの使い方の詳細については、「COM インターフェイスの使い方」を参照すること。