コンテンツにスキップ

Component Object Model

出典: フリー百科事典『ウィキペディア(Wikipedia)』

これはこのページの過去の版です。240f:76:d53:1:dd1d:8092:20c9:ac91 (会話) による 2019年6月15日 (土) 09:07個人設定で未設定ならUTC)時点の版であり、現在の版とは大きく異なる場合があります。

Component Object Model(COM、コンポーネント オブジェクト モデル)とは、プログラミング言語と提供形態( DLL・EXE )に透過的なクラスライブラリの実装と利用を目的とした、マイクロソフトが策定した仕様のこと。アプリケーションソフトウェア間での通信や、オペレーティングシステムとアプリケーションソフトウェアとのAPIに用いられる。

COMを使用して開発されたソフトウェア部品をCOMコンポーネントと呼ぶ。これは一般のオブジェクト指向言語における、クラス(COMのクラスなのでコクラスと呼ぶ)又はそのインスタンスを指す。開発には特定のプログラミング言語に依存せず、C言語C++Visual BasicSmalltalkJava等、COMのABIを生成できる言語で行える。とは言っても、最も親和性が高い( コンパイラに、COM ABI生成を行う機能を 別途付けずに済む )C++が専ら多用される。

COMという用語はソフトウェア開発の世界ではOLE、OLEオートメーション、ActiveX、COM+、DCOMをカバーする包括的な用語としてよく使われる。COMコンポーネントは、他ソフトウェアと通信するためのインタフェースを有している。アプリケーションソフトウェアは、公開されているインタフェースを介してCOMコンポーネントと通信をし、それらを組み合わせることでサービスを提供する。言語によるメモリやその他計算資源の割り付けの違いは、参照カウントを利用してオブジェクトの生成と破棄をそのオブジェクト自身の責任とすることにより解決する。オブジェクトの異なるインタフェース間のキャストはQueryInterface関数で行う。メソッド呼び出しをデリゲート(委譲)する形でサブオブジェクトの集合(アグリゲーションと呼ぶ)を生成する方法がCOM内における最適な継承方法である。

COMはUNIXMac OSでも使う事ができると言われるが、実質Microsoft Windows専用である。何故なら、COM仕様に則るライブラリの実装・利用には、レジストリ( Windows専用の非SQL型DB )・マニフェストファイル・各COMシステムコールというWindowsに密着した機能を経なければならないためである。

これは後で詳述する。これらに相当する機能はUNIXやMac OSには実装も計画もない。XPCOMというクロスプラットフォーム用のCOM仕様が辛うじてあるが、廃れているに等しい。

COMは.NET Frameworkに置き換えられているものも多い。たとえば.NETはDCOMの代替として、Windows Communication Foundation (WCF) を通じてWebサービスをサポートする。WCFがXMLベースのSOAPメッセージを利用するのに対し、ネットワークで接続されたDCOMはバイナリの独自仕様フォーマットを利用する。しかし、Microsoft DirectXなどに代表されるように、ネイティブC++での利用を前提とした速度性能が重視されるクラスライブラリ実装には、依然として.NETではなくCOMが使われる傾向にある。

COMはまたソフトウェアコンポーネントシステムとしてCORBAJava Beansと競合関係にある。

COMの歴史

  • 1991年、COMの前身であるOLEが、OLE 1としてWindows 3.1とともに公開された。
  • 1992年、OLE 2が公開された。IUnknownインタフェースなど、のちにCOMと改称される要素の多くがOLE 2で登場した。
  • 1994年、OCXもしくはOLEコントロールがVBXコントロールの後継として紹介される。それと同時に、OLEは、もはや単なる頭文字ではなく、コンポーネント技術を表す用語となった。
  • 1996年初頭、マイクロソフトは、OLEのうちでインターネットと関連のあるいくつかの技術をActiveXとして名称変更した。やがて、OLEとして公開されていた技術がActiveXに統合され始める。
  • 1997年、マイクロソフトは再びコンポーネントを使用するこれらの技術の変名を行い、Component Object Modelとした。

関連技術

COMはWindowsで主要なソフトウェア開発プラットフォームであり、数多くの技術の開発に影響を与えた。

COM+

エンタープライズレベルのオペレーティングシステム (OS) の代替としてWindowsのポジションを確立するためだけでなく、分散トランザクションをサポートし、メモリとプロセッサ(スレッド)の管理の改善するため、マイクロソフトはWindows NT 4.0 Service Pack 4でMicrosoft Transaction Server (MTS) を導入した。

Windows 2000でのCOMの重要な拡張は(MTSによる外部ツールの組み合わせとは対照的に)OSへ統合することであり、これをCOM+と改名した。この時点でマイクロソフトは個別の要素としてDCOMを重視していなかった。COM+の追加レイヤでトランザクショナルCOMコンポーネントを直接的に取り扱った。COM+コンポーネントはコンポーネントサービスアプリケーションインタフェースを通じて追加された。

COM+は「コンポーネントファーム (component farms)」で動作できる利点があった。コードが正しい場合、コンポーネントはメモリからアンロードすることなく初期化ルーチンを新たに呼び出すことにより再利用できた。以前はDCOMだけで可能だったコンポーネントの分散化(他のマシンからの呼び出し)も可能となった。

またCOM+では、COM+イベントという、サブスクライバ/パブリッシャー型のイベントメカニズムを導入し、アプリケーション間非同期メッセージングプロトコルであるMSMQを利用するための新たな方法として、Queued Componentsというコンポーネントを介するモデルを提供した。これにより、COM+プログラミングモデルでは、遅延バインディングされたイベント、および、パブリッシャー/サブスクライバとイベントシステムの間のメソッド呼び出しがサポートされる。

DCOM

.NET

COMプラットフォームは.NET Frameworkに大幅に取って代わられ、マイクロソフトは.NETに注力する戦略に集中している。COMは複雑で高性能なVisual BasicASPで実装されたフロントエンドのコードと接続するためによく利用されていた。

好感されている.NETに対して、COMはある程度批判されている。.NETがWindows FormsWeb Formの両方に対してジャストインタイムコンパイル方式と共にVisual Basicに似たラピッドデベロップメントツールを提供するため、バックエンドコードはC#Visual Basic .NETC++/CLIを含むあらゆる.NET言語で実装できる。

それでもCOMはまだ様々なテクノロジーで重要なソフトウェアの基盤として生き延びている。例えば上述のように、DirectX APIはCOMに則っている。2015年にWindows 10とともにリリースされたDirectX 12 (Direct3D 12) も、依然としてCOMベースであり、C++を第1級言語としている。2018年時点では、マイクロソフトはCOMのサポートやCOMそのものをやめてしまう計画を持っていない。

COMはまた、コンパイル時点でAPIの情報を必要としないスクリプトからCOMオブジェクトを呼び出すためのインタフェース(動的ダックタイピング)を提供するため、Microsoft OfficeInternet Explorerのようなネイティブアプリケーションのスクリプト制御のための技術として理想的でもある。COMのあらゆる要素を一意に識別するためのGUID( マイクロソフトによるUUIDの実装 )は、COM以外でも独自のIDが必要とされる場合に広く利用されている。

トランザクションやコンポーネントのキューといったようなCOM+が提供する複数のサービスはエンタープライズな.NETアプリケーションでもまだ重要である。

制約はあるものの、.NETはCOMに対して相互運用性のサポートがある。.NETはランタイム呼び出し可能ラッパー (RCW) を実装することでCOMオブジェクトを利用できる。COMクライアントはCOM呼び出し可能ラッパー (CCW) によって、特定の制約に従った.NETオブジェクトを利用できる。COMと.NETの両方の側からは相手側のオブジェクトがネイティブなオブジェクトに見える。

.NET Remotingでは、オブジェクトがプロセスやマシンの境界を越えてリファレンスや値を透過的にマーシャリングできるようにして、COMが持つ数多くのリモート実行の欠点を解決する。

Windowsランタイム

Windows 8およびWindows RTにて、新たなアプリケーションの開発・実行基盤としてWindowsランタイム (WinRT) が導入された。WinRTはCOMを拡張したネイティブ技術であるが、Windowsメタデータ (WinMD) および言語プロジェクション (language projection) と呼ばれる技術により、.NET言語やJavaScriptなどからも透過的に利用可能である[1]

技術的詳細

COMコンポーネントにはクラスID (CLSID。上述GUIDを使っている) が割り当てられ、COMコンポーネント同士はクラスIDによって区別される。各COMコンポーネントは1つ以上のインタフェースを公開することで機能を提供している。インタフェース同士はインタフェースID (IID) で区別される。IIDにもGUIDを使っている。

COMインタフェースはプログラミング言語とCOMとを結び付けている。COMコンポーネントへのアクセスは全てインタフェースを通さなければならない。これによって、プロセスやコンピュータを跨いでCOMコンポーネントにアクセスすることができるようになっている(コンピュータを跨いでのアクセスにはDCOMが必要)。

インタフェース

全てのCOMコンポーネントはIUnknownと呼ばれるインタフェースを継承する必要がある。また、全てのCOMインタフェースはIUnknownから派生している。IUnknownは、AddRef / Release / QueryInterfaceという3つのメソッドを持っている。AddRefReleaseはインタフェースの生存期間を管理するための参照カウントを実装するメソッドである。そしてQueryInterfaceは、IIDを指定しコンポーネントが実装している他のインタフェースを取得するメソッドである。これはC++dynamic_castや、JavaおよびC#型変換演算子に相当する。

COMコンポーネントのインタフェースは、反射性、対称性、推移性を備えている必要がある。反射性とは、あるインタフェースから、QueryInterfaceをそのインタフェース自身を表すIIDを与えて呼び出すと、返ってくるインタフェースは元と同じものでなければならないということである。対称性とは、インタフェースAからQueryInterfaceでインタフェースBが取得できる場合、インタフェースBからインタフェースAが取得できなければならないということである。推移性とは、対称性と似ているが、インタフェースBがインタフェースAから取得でき、インタフェースCがインタフェースBから取得できる場合、インタフェースCはインタフェースAから取得できなければならないということである。

インタフェースには、C++でいう仮想関数テーブルへのポインタが含まれている。この構造は、OLE 1.0のときからOLEシステムとの通信に使われていた。

COMはコンポーネント間の通信のためにほかにも多くの標準インタフェースを定めている。たとえばデータストリームを管理するIStreamが挙げられる。これはファイルストリームのコンポーネントがファイルを読み書きする際に使うことが考えられる。IStreamReadメソッドとWriteメソッドはストリームに対して読み取りと書き込みを行うことが想定される。他の例として、IOleObjectは、呼び出した側がコンポーネントの境界を決められるメソッドを持っており、また「開く」、「保存」などの操作を行うことができる。

コクラス

既述の通り、COMではクラスのことをコクラス (coclass) と呼ぶ。コクラスは、COMにおける(オブジェクト指向プログラミングのような)クラスを定義する言語非依存の手段である。

1つのコクラスは、1つ以上のインタフェースの実装を提供する。COMに対応しているプログラミング言語であれば、C++Visual Basicなどどんな言語でも実装を行うことができる。

個々のコクラスは、クラスID (CLSID)やProgIDで識別される。

  • クラスIDはGUIDを使った表現である。通常、各コクラスに対しクラスIDを1つ割り当てる。ただし、互換性のため、旧バージョンのクラスIDに新バージョンのコクラス実装を登録し、結果的に複数クラスIDが同一コクラスを指し示す場合もある。そのような用途にCoTreatAsClass関数が用意されている。
  • ProgIDは文字列による表現である。InternetExplorer.ApplicationやMsxml2.DOMDocument.6.0など「プログラム名.コンポーネント.バージョン」(バージョンは非必須)という規則である[2]。ProgIDは必須ではなく、コクラスによってはProgIDを持っていない場合もある。

ProgIDによる指定でオブジェクトを作成・取得する際には、ProgIDからクラスIDに変換する必要がある。この変換は一意ではなく、特にバージョン指定のないProgIDでは、インストールされているコンポーネントのバージョン次第で、コンピューターごとに異なるCLSIDとなる可能性がある。

この処理はプログラミング言語やライブラリによって実装される場合もある。VBScriptなど、オブジェクト作成時にProgIDでの指定のみ可能なプログラミング言語もある。

COMは、Windows開発の世界に、実装からインタフェースを切り分けるという概念を意識させることをもたらした。これは、今日プログラマがシステム構築に影響を与えることになった。この基本的な概念の延長に、1つのインタフェースに対して複数の実装を用意し、多態させるという考えが挙がった。これは、アプリケーションが実行時にいくつもの実装から選べ、それを区別なく同様に操作できるということである。

インタフェース定義言語とタイプライブラリ

COMコンポーネントに関する情報の記述手段として、MIDL(マイクロソフトインタフェース定義言語、IDLも参照)やタイプライブラリがある。MIDLはテキストファイルで主として開発時に用いられる。タイプライブラリはバイナリファイルであり、C/C++におけるヘッダファイルのような役目(型情報の提供)を、他のプログラミング言語用に用意した物で、開発時だけでなく実行時に参照されることも想定されている。

COMコンポーネントの開発では、普通IDLで型を定義することから始める。IDLファイルではオブジェクト指向的なクラスやインタフェース、構造体、列挙体などのユーザ定義型をプログラミング言語に依存せず記述できる。このIDLではC/C++に似たキーワードと、それに追加してインタフェースを定義する「interface」、コクラスを定義する「coclass」、それらの集合を現す「library」という2つのキーワードを使用する。また各宣言の前に角括弧[]で括って属性を指定でき、インタフェースにGUID (IID) を指定したり配列引数とその長さを示す引数との関係を指示したりできる。

IDLファイルはMIDLコンパイラで様々なプログラミング言語に向けてコンパイルされる。この際、C/C++用にはインタフェースなどが宣言されたヘッダファイルとGUIDを定義したCのソースファイル、またCOMのメソッド呼び出しをRPC用に変換する「プロキシ」とそれを元に戻す「スタブ」のソースファイルも生成される。又 タイプライブラリ(.TLBファイル)も作られる。タイプライブラリに含まれているバイナリのメタデータはコンパイラや実行環境(Visual BasicやDelphi、.NETのCLRなど)で利用される。その結果タイプライブラリファイルで定義されたコクラスをその言語や環境に持ち込んで使用できるようになる。

オブジェクトフレームワークとしてのCOM

COMの基本原則はそれがオブジェクト指向の哲学に基づいているということである。オブジェクト指向開発と実装を実現するためのプラットフォームである。

COMはランタイムフレームワークであるため、タイプは明示的に識別可能でありランタイム時に指定可能である必要がある。これを実現するためにGUIDが使われる。それぞれのCOMのタイプはランタイム時(コンパイル時)に自分の識別用GUIDを指名する。

COMのタイプについての情報がコンパイル時とランタイム時の両方でアクセスできるようにする必要性から、COMはタイプライブラリを提供する。COMがオブジェクトの相互作用のための動的なフレームワークとしてその能力を発揮するのはタイプライブラリが効果的に利用されているからである。

以下の例にあるIDLのコクラス定義を考える。

coclass MyObject 
{ 
  [default] interface IMyObject; 
  [default, source] dispinterface _IMyObjectEvents; 
};

上記のコードの断片は、IMyObjectというインタフェースを実装しなければならず、_IMyObjectEventsというイベントインタフェースを(実装ではなく)サポートしなければならない、MyObjectというCOMのクラスを宣言している。

イベントインタフェースの部分はさておき、これは概念的には次のようなC++のクラスを定義することに等しい。

class MyObject : public IMyObject 
{ 
  ... 
  ... 
  ... 
};

IMyObjectはC++の純粋基底クラスに相当するところである。

COMクラスのMyObjectを振り返ってみよう。このコクラスの定義がIDLで形式化されてタイプライブラリがコンパイルされると、個別のプログラミング言語のコンパイラはこのタイプライブラリを読み込んで正しく解釈し、そして(特定のコンパイラで)何らかのコードを生成して、最終的にはMyObjectコクラスであるCOMと考えられるバイナリの実行コードが生成される。

COMコクラスの実装がビルドされシステムで利用可能になると、次にどのようにしてインスタンス化されるのかという疑問が起きる。C++のような言語で、このコクラスを利用したいコクラスから、(IID_IMyObjectというIIDで指定した)インタフェースと同じように、このコクラスのCLSIDを指定するCoCreateInstance() APIを利用できる。CoCreateInstance()の呼び出しは以下の通り。

CoCreateInstance(
  CLSID_MyObject, 
  NULL, 
  CLSCTX_INPROC_SERVER, 
  IID_IMyObject, 
  (void**)&m_pIMyObject 
);

これは概念的に次のようなC++のコードと等しい。

IMyObject* pIMyObject = new MyObject();

前者はIMyObjectインタフェースを実装するオブジェクトへのポインタを取得したいというCOMサブシステムを表しており、このインタフェースのCLSID_MyObjectの特定の実装を要求している。後者はIMyObjectインタフェースを実装するC++のクラスのインタフェースを生成したいということを表しており、C++のクラスとしてMyObjectを使用している。

そしてコクラスはCOMの世界ではオブジェクト指向のクラスである。コクラスの主要機能は、(1) バイナリの性質と、(2) 結果的にプログラミング言語に非依存ということである。

レジストリ

Windowsの場合、COMのクラス、インターフェイス、タイプライブラリはWindowsレジストリにあるGUIDのリストであり、クラスは"HKEY_CLASSES_ROOT\CLSID"に、インターフェイスは"HKEY_CLASSES_ROOT\interface"にある。COMライブラリは各COMオブジェクトが正しいローカルライブラリを特定するためやリモートサービスのネットワーク上の位置を特定するためにレジストリを使用する。DLLサーバー型のCOMコンポーネントはregsvr32英語版というコマンドラインプログラムを使用してレジストリ登録および登録解除を行なうが、EXEサーバー型のCOMコンポーネントは自身に/RegServerあるいは/UnRegServerスイッチを付けて起動し、レジストリ登録および登録解除を行なう[3]

システムレジストリを使用する性質上、レジストリ登録されたCOMコンポーネントはコンピュータあるいはネットワーク上のすべてのCOMクライアントから共有される。COMコンポーネントをバージョンアップする際にインターフェイスを変更・追加する場合、旧バージョンをレジストリ登録解除したうえで新バージョンをレジストリ登録し直すか、異なる名前およびGUIDを持つインターフェイスを改めて定義した別のコンポーネントをレジストリ登録する必要がある。前者はバージョンアップの際にCOMクライアントの更新は不要だが、破壊的な仕様変更を避けるなど後方互換性に配慮した設計が求められる。後者は新しいバージョンに対応するCOMクライアントを再作成する必要があるが、複数のバージョンを共存させることができ、バージョン間の互換性を考慮する必要はない。たとえばDirect3Dのバージョン7/8/9/10/11/12は、すべて異なるインターフェイスを持つ独立したCOMコンポーネント[4]であり、同一コンピュータ上に共存できる。典型的にいって、COMにおける最良のアプローチは、機能を拡張するためには新しいインターフェイスを定義することである[5]。なおCOMコンポーネントのマイナーバージョンアップの際は、通例既存のインターフェイスを継承して新たなインターフェイスを定義する手法が採られる。たとえばDirect3D 10.1あるいは11.1/11.2/11.3における各インターフェイスは、Direct3D 10.0あるいは11.0のインターフェイスからそれぞれ派生した新しいインターフェイスを定義する方法で機能拡張が行なわれている。

Windows XP以降では、「分離アプリケーションとSide-by-Sideアセンブリ」と呼ばれる仕組みを使い、レジストリではなくマニフェストファイルを使用してコンポーネント依存関係を解決することも可能である。この仕組みを用いることで、COMコンポーネントをシステム全体で共有せず、アプリケーションごとにプライベートアセンブリとして配布・運用することも可能となる。

参照カウント

最も基本的なCOMのインタフェースであるIUnknown(全てのCOMインタフェースはここから派生する)は、QueryInterfaceによる機能の確認と、AddRef()およびRelease()を通じたオブジェクトの寿命の管理という2つの主要な概念をサポートする[6]。参照カウントと機能の確認は(オブジェクトの各インタフェースに対してではなく)オブジェクトに対して適用される。従って注意深く実装する必要がある。

インタフェースへのアクセスを要求したクライアントが存在している限り個々のオブジェクトが生存していることを保障し、逆にオブジェクトを使用していたすべてのコードが終了してそのオブジェクトが不要になった時にオブジェクトを適切に処分するため、インタフェースの参照カウントというテクニックがCOMでは要求される。COMオブジェクトは参照カウントが0に達したときに自分のメモリを自分で解放する責任がある。

これを実装するため、COMオブジェクトは一般的に参照カウントのための整数値を持つ。オブジェクトのインタフェースからAddRef()が呼ばれるとこの整数値が加算される。Release()が呼ばれるとこの整数値は減算される。AddRef()とRelease()はCOMオブジェクトのクライアントがそのオブジェクトの寿命に関与できる唯一の手段である。内部の整数値はCOMオブジェクトのプライベートなメンバーであり、直接的にはアクセスできない。

AddRef()の目的は、COMオブジェクトへの新しい参照が生じて、その参照が正当である限り生存し続ける必要があるということを、そのCOMオブジェクトに対して示すことである。Release()の目的は、クライアント(またはクライアントコードの一部)がもうオブジェクトを必要としなくなり、もし参照カウントが0に達した場合にはオブジェクトが自らを破棄するであろうということを示すことである。

一部の言語(例えばVisual Basic)では自動参照カウントが提供されるため、COMオブジェクトの開発者はソースコード中で内部参照カウントを明示的に保持する必要がない。C言語でCOMを利用する場合、明示的な参照カウントの操作が必要である。C++では、自分自身でそれを管理することもできるし、参照カウントを全部管理してくれるスマートポインタ(ATL::CComPtrなど)を利用することも選択できる。

下記はCOMオブジェクトで適切な参照カウントの制御を簡単にするためのAddRef()とRelease()を呼び出す際の一般的なガイドラインである。

  • (戻り値またはoutパラメーターで)インタフェースの参照を返す関数(オブジェクトメソッドとグローバル関数のいずれも)は、それを返却する前に、背後にあるオブジェクトの参照カウントを加算しておくべきである。従って関数やメソッドの内部で、AddRef()が(返却する)インタフェースの参照に対して呼び出される。IUnknownインタフェースのQueryInterface()メソッドはこの実例である。従って、リターンされたインタフェースの参照は既に加算されており、リターンされたインタフェースの参照のAddRefを再度呼びだす必要がないということを開発者は理解していなければならない。
  • インタフェースのポインタが上書きされるかスコープから外れる前にインタフェースの参照からRelease()を呼び出さなければならない。
  • インタフェースの参照ポインタからコピーを作る場合、そのポインタでAddRef()を呼び出すべきである。結局この場合は、背後にあるオブジェクトのもう1つの参照を実際に作成している。
  • インタフェースを参照するだけで内部リソースを割り当てる必要があることからインタフェース毎に参照をカウントするようにオブジェクトが実装されているかもしれないため、参照した特定のインタフェースに対してAddRef()とRelease()を呼び出さなければならない。
  • これらの関数の追加の呼び出しはケーブルを超えてリモートオブジェクトに送信されない。プロキシはリモートオブジェクトの参照を1つだけ保持し、ローカルの参照カウントを管理する。

COMの開発を容易にして促進するため、マイクロソフトはC++の開発者のためにActive Template Library (ATL) を導入した。ATLは高レベルのCOM開発パラダイムを提供する。ATLはまた、スマートポインタオブジェクトを提供することによってCOMクライアントのアプリケーション開発者が参照カウントを直接管理しなくてもよいようにする。

その他にはMFCVBScript、Visual Basic、ECMAScript (JavaScript)、Delphiなどのライブラリや言語がCOMに対応している。

インストール

COMはクラスファクトリを利用してCOMオブジェクトの生成プロセスを標準化する。COMオブジェクトを作成するため、2つの関連した項目が存在していなければならない。

  • クラスID
  • クラスファクトリ

各COMクラスまたはコクラスはユニークなクラスID (GUID) で関連付けられていなければならない。またクラスファクトリとも関連付けられていなければならない(レジストリを利用して行う)。クラスファクトリ自身はCOMオブジェクトである。これはIClassFactoryまたはIClassFactory2インタフェースを持つオブジェクトでなければならない(後者はライセンス管理をサポートしているインタフェースである)。これらのオブジェクトは他のオブジェクトを生成する責任がある。

クラスファクトリオブジェクトは一般的にはCOMオブジェクト自身と同じ実行ファイル内(つまりサーバーコード内)に含まれている。ターゲットオブジェクトを作成するようにクラスファクトリを呼び出すとき、このターゲットオブジェクトのクラスIDが提供されていなければならない。このようにしてクラスファクトリはどのクラスのオブジェクトをインスタンス化するのかを把握する。

単一のクラスファクトリオブジェクトは複数のクラスのオブジェクトを生成するかもしれない。すなわち、異なるクラスIDを持つ2つのオブジェクトが同じクラスファクトリオブジェクトによって生成されるかもしれない。しかしながら、これはCOMシステムにとっては曖昧なものではない。

別のオブジェクトにオブジェクト生成の責任を委ねることにより、抽象度が非常に高くなり、開発者に高い柔軟性をもたらす。例えば、シングルトンやその他のオブジェクト生成パターンの実装が容易になる。またファクトリオブジェクトはCOMオブジェクトのメモリ割り当てからアプリケーションの呼び出しを守る。

クライアントアプリケーションがクラスファクトリオブジェクトを入手できるようにする必要性から、COMサーバーはそれらを適切に公開しなければならない。クラスファクトリはサーバーコードの特質上、別の方法で公開される。DLL サーバーはDllGetClassObject()というグローバル関数をエクスポートしなければならない。EXEサーバーは実行時にCoRegisterClassObject()というWindows API関数でクラスファクトリを登録しなければならない。

下記はクラスファクトリを利用したオブジェクト生成のシーケンスの一般的な概略である。

  1. オブジェクトのクラスファクトリをCoGetClassObject()というAPI(Windowsの標準API)で取得する。
    CoGetClassObject()を呼び出すためには作成するオブジェクトのクラスIDが提供されていなければならない。C++によるコード例を以下に示す。
    IClassFactory* pIClassFactory = NULL;
    
    CoGetClassObject
    (
      CLSID_SomeObject,
      CLSCTX_ALL,
      NULL,
      IID_IClassFactory,
      (LPVOID*)&pIClassFactory
    );
    
    上記のコードはCLSID_SomeObjectというクラスIDによって識別されたCOMオブジェクトのクラスファクトリが必要であることを示している。このクラスファクトリオブジェクトはIClassFactoryインタフェースで返される。
  2. 返却されたクラスファクトリオブジェクトを使って元々意図していたCOMオブジェクトのインタフェースを生成するように要求する。C++によるコード例を以下に示す。
    ISomeObject* pISomeObject = NULL;
    
    if (pIClassFactory)
    {
      pIClassFactory->CreateInstance
      (
        NULL,
        IID_ISomeObject,
        (LPVOID*)&pISomeObject
      ); 
    
      pIClassFactory->Release();
    
      pIClassFactory = NULL;
    }
    
    上記のコードは、クラスファクトリオブジェクトのCreateInstance()メソッドを使用して、IID_ISomeObjectというGUIDで識別されるインタフェースを公開しているオブジェクトを生成することを示している。このオブジェクトのISomeObjectインタフェースへのポインタが返される。クラスファクトリオブジェクトはそれ自身がCOMオブジェクトであることから、もう必要なければリリースする必要があるということにも注意してほしい(要するにこれのRelease()メソッドを呼び出さなければならない)。

上記はオブジェクトをインスタンス化するクラスファクトリの非常に基本的な使用例である。上位レベルのコンストラクタも利用可能であり、その一部はWindows APIを直接利用しないものもある。

例えば、アプリケーションはオブジェクトのクラスファクトリを取得せずにCOMオブジェクトを直接生成するためにCoCreateInstance() APIを利用できる。しかしながら、CoCreateInstance() APIはオブジェクトのクラスファクトリを取得するためにCoGetClassObject() APIを内部で呼び出しており、そしてCOMオブジェクトを生成するためにクラスファクトリのCreateInstance()メソッドを使用している。

CreateObject()というオブジェクトをインスタンス化するためのグローバル関数の他に、C++ではnewを利用できる。C++のコンストラクタはIClassFactory::CreateInstance()メソッドを呼び出して(CoGetClassObject() APIで)目的のオブジェクトのクラスファクトリオブジェクトを取得することを隠蔽する。

PowerBuilderのPowerScriptのように上位レベルのオブジェクトを生成するコンストラクタを提供する言語もある。しかしながらCoGetClassObject()とIClassFactoryインタフェースを使った非常に基本的なオブジェクト生成手法もまだ利用できる。

COMの初期の時代、オブジェクトがどのような機能を提供するのかということをクライアントから確認するためには、インスタンスを実際に生成して(IUnknownインタフェースの)QueryInterfaceメソッドを呼び出してみるしかなかった。

この検証方法は、特定の業務のために適切なコンポーネントを選択したり、オブジェクトが提供するメソッドの使用方法を開発者が理解できるようにしたりといったところで、多くのアプリケーションにとって不便であるということになった。

結果的にコンポーネントを完全に確認できるCOMタイプライブラリが導入された。タイプライブラリは、コンポーネントのCLSID、コンポーネント実装のインタフェースのIID、これらのインタフェースの各メソッドの解説といった情報を含んでいる。タイプライブラリはVisual BasicVisual StudioのようなRAD環境でクライアントアプリケーションの開発者をアシストするために一般に利用されている。

プログラミング

COMはバイナリ標準(言語からは認知できない (language agnostic) とも言う)であり、バイナリで定義されたデータ型とインタフェースを解釈して実装する機能を使ってどんなプログラミング言語でも開発できる。

ランタイムライブラリ(厳密に言えばプログラマ)は、COMの環境に立ち入って、インスタンス化してCOMオブジェクトの参照をカウントし、バージョン情報からオブジェクトを問い合わせて、新しいバージョンのオブジェクトを利用してコーディングし、新しいバージョンが利用可能でない場合は古いバージョンでも動作するようにフォールトレトラントをコーディングするといった責任がある。

アプリケーションとネットワークの透過性

プロセス内から、またはコンピューター内のプロセスの境界を越えて、あるいはDCOMテクノロジを利用してネットワークを超えて、COMオブジェクトをインスタンス化して参照できる。

プロセスの外やリモートにあるオブジェクトはメソッドコールや戻り値をやりとりするためにマーシャリングを利用できる。

マーシャリングでは、オブジェクトや、オブジェクトを利用しているコードは見えない。

COMのスレッド

COMでは、アパートメントモデルとして知られているコンセプトによってスレッドの問題を解決している。ここで言うアパートとは、単一のスレッドまたはスレッドのグループの中にある実行コンテキストが1つまたは複数のCOMオブジェクトに関連づけられていることを指している。

アパートメントでは以下のガイドラインが関連するスレッドとオブジェクトのために示される。

  • 1つのCOMオブジェクトは1つのアパートメントだけに関連づけられる。オブジェクトが実行時に生成された時点で関連づけられる。オブジェクトの初期化後は一生そのアパートメントに属する。
  • COMスレッド(つまりCOMオブジェクトを生成したか、COMのメソッドコールが行われたスレッド)もまた1つのアパートメントに関連づけられる。COMオブジェクトと同様、スレッドとアパートメントの関連づけは初期化時に決定される。各COMのスレッドもまた終了するまで指定されたアパートメントに属し続ける。
  • メソッドを呼び出すスレッドとオブジェクトが同じアパートメントに属す場合、COMの介入無しに直接呼び出される。
  • メソッドを呼び出すスレッドとオブジェクトが異なるアパートメントに属す場合、そのメソッド呼び出しはマーシャリングを利用して行われる。これにはプロキシとスタブが利用される。

COMの世界では、シングルスレッドアパートメント (STA)マルチスレッドアパートメント (MTA)中立アパートメント (NA) の3つのアパートメントモデルがある。各アパートメントは、オブジェクトの内部状態が複数のスレッドを超えて同期されるかどうかという1つのメカニズムを表している。

シングルスレッドアパートメントモデルは非常に一般的に利用されているモデルである。ここではCOMオブジェクトはデスクトップアプリケーションのユーザーインタフェースに似た立場にある。STAモデルでは、単一のスレッドがオブジェクトのメソッドを動かすことに専念している。つまり常に単一のスレッドでオブジェクトのメソッドが実行される。このようなアパートメントでは、アパートメントの外にあるスレッドからのメソッドコールはマーシャリングされ、(標準のWindowsのメッセージキューを利用して)システムによって自動的にキューに入れられる。これによりその呼び出しが完了してから各オブジェクトのメソッド呼び出しが実行されるようになり、レースコンディションや同期性の欠如といった心配がない。プロセスの中で最初に作られたSTAは特にメインSTAと呼ばれ、マルチスレッドを全く考慮していないCOMオブジェクトはメインSTAだけで動作させられる。

COMオブジェクトのメソッドの同期を自分で取りたい場合、メソッドを呼び出したスレッドと同一のスレッドでメソッドを処理するようにできる。これをマルチプルスレッドアパートメントと呼ぶ。ただし、STAスレッドからのMTAオブジェクトのメソッド呼び出しはマーシャリングされる。プロセスは複数のCOMオブジェクトで構成でき、その一部をSTAにしてそれ以外はMTAを利用するというようにできる。

COM+で導入されたスレッド中立アパートメントは、オブジェクトのメソッド呼び出しを管理する必要がない場合に用いられる。唯一の条件はオブジェクトの全てのメソッドがリエントラント(再入可能)でなければならないということである。中立アパートメントに属すオブジェクトのメソッドは、STAスレッド・MTAスレッド、どちらからでもマーシャリングなしに直接呼び出せる。

関連項目

参考文献

  • Dale Rogerson 著、バウン グローバル株式会社 訳『Inside COM』アスキー出版局、1997年。ISBN 4-756-12176-4 

脚注

外部リンク