Component Object Model(COM、コンポーネント オブジェクト モデル)とは、マイクロソフトが提唱するソフトウェアの再利用を目的とした技術のことである。COMは相互作用するバイナリソフトウェアコンポーネントを作成するための、プラットフォーム非依存・分散型・オブジェクト指向のシステムであると説明されているComponent Object Model (COM) - Windows applications | Microsoft DocsThe Component Object Model - Windows applications | Microsoft Docs。具体的にはアプリケーションソフトウェア間の通信や、オペレーティングシステムとアプリケーションソフトウェアとのインターフェイス(API)に用いられる。

COMを使用して開発されたソフトウェア部品をCOMコンポーネントと呼ぶ。COMコンポーネントの開発は、特定のプログラミング言語に依存せず、C言語やC++、Visual Basic、Smalltalk、Javaなど、様々な言語により開発することができる。COMという用語は、OLE、OLEオートメーション、ActiveX、COM+、DCOMの総称としてよく使われる。COMコンポーネントは、他ソフトウェアと通信するためのインターフェイスを有している。アプリケーションソフトウェア(COMクライアント)は、通信インターフェイスを表現する抽象型として公開されているCOMインターフェイスを介してCOMコンポーネント(COMサーバー)と通信をし、それらを組み合わせることでサービスを提供する。言語によるメモリやその他計算資源の割り付けの違いは、参照カウントを利用してオブジェクトの生成と破棄をそのオブジェクト自身の責任とすることにより解決する。オブジェクトがサポートする、異なるインターフェイス間の型変換は、言語固有のキャスト構文ではなく、言語非依存のQueryInterfaceメソッドで行う。メソッド呼び出しをデリゲート(委譲)する形でサブオブジェクトの集合(アグリゲーションと呼ぶ)を生成する方法がCOM内における最適な継承方法である。

COMはプロトコルなどの仕様が公開されており[MC-COMQC]: Component Object Model Plus (COM+) Queued Components Protocol | Microsoft Docs、XPCOMのようなクロスプラットフォーム実装も存在するが、COMが主に利用されているのはMicrosoft Windows環境である。MonoにおけるCOM相互運用もMS COMおよびXPCOMを基盤としているが、機能およびサポート環境はまだ限定されているThe Mono Runtime | MonoCOM Interop | Mono。

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

COMはまたソフトウェアコンポーネントシステムとしてCORBAやJava 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 BasicやASPで実装されたフロントエンドのコードと接続するためによく利用されていた。

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

それでもCOMはまだ様々なテクノロジーで重要なソフトウェアの基盤として生き延びている。例えばマルチメディアAPIとして広く普及しているDirectXのAPIはCOMをベースにしているDirectXのCOMは主にC++から使われることを想定した独自実装となっており、標準的なCOMの流儀に則っていない部分もある。オブジェクトの生成にCoCreateInstance()関数を利用せずD3D11CreateDevice()のような独自のファクトリ関数を用意している、IXAudio2SourceVoiceのようにIUnknown派生でないインターフェイスが存在している、といった具合である。。2015年にWindows 10とともにリリースされたDirectX 12 (Direct3D 12) も、依然としてCOMベースであり、C++を第1級言語としている。2018年時点では、マイクロソフトはCOMのサポートやCOMそのものをやめてしまう計画を持っていない。

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

トランザクションやコンポーネントのキューといったような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などからも透過的に利用可能であるWindows 8時代のアプリ開発とWinRT - @IT。

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

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

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

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

インターフェイスには、インターフェイスを実装した関数へのポインタをその宣言時の順に並べた仮想関数テーブルへのポインタが含まれている。この関数テーブル構造は、OLE 1.0のときからOLEシステムとの通信に使われていた。

COMはコンポーネント間の通信のためにほかにも多くの標準インターフェイスを定めている。たとえばデータストリームを管理するIStreamが挙げられる。これはファイルストリームのコンポーネントがファイルを読み書きする際に使うことが考えられる。IStreamのReadメソッドとWriteメソッドはストリームに対して読み取りと書き込みを行うことが想定される。他の例として、IOleObjectIOleObject (oleidl.h) - Win32 apps | Microsoft Docsは、あるアプリケーションのデータオブジェクト内に別のアプリケーション向けのデータオブジェクトを埋め込むような複合文書に利用される。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など「プログラム名.コンポーネント.バージョン」(バージョンは非必須)という規則である。ProgIDは必須ではなく、コクラスによってはProgIDを持っていない場合もある。

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

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

COMは、Windows開発の世界に、実装とインターフェイスを切り離すという概念を意識させることをもたらした。この基本的な概念の延長には、オブジェクト指向におけるポリモーフィズム(多態)の実現がある。すなわち、1つのインターフェイスに対して複数の実装(COMサーバー)を用意し、アプリケーション(COMクライアント)側が任意の実装を選び、それを区別なく一様に操作することができる例えば、DAOでは呼び出し先コンポーネントがSQL Server用実装であるか、Oracle DB用実装であるかを意識することなく、クライアントアプリケーションを記述できる。ということであり、またCOMコンポーネントが要求するインターフェイスを実装するオブジェクトをアプリケーション側で用意して、COMコンポーネントにインターフェイスを通じて操作させることで、コールバック処理などのカスタマイズを実現することもできるということである。後者はイベントシンク (event sink) として標準化され、プロセス内だけでなくDCOMプロセス間の透過的なコールバック処理をも実現することにつながった。

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

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

IDLファイルはMIDLコンパイラで様々なプログラミング言語に向けてコンパイルされる。C/C++用にはインターフェイスなどが宣言されたヘッダファイルとGUIDを定義したCのソースファイル、またCOMのメソッド呼び出しをRPC用に変換する「プロキシ」とそれを元に戻す「スタブ」のソースファイルも生成される。

MIDLはIDLファイルからタイプライブラリ(.TLBファイル)も作る。タイプライブラリに含まれているバイナリのメタデータはコンパイラや実行環境(Visual BasicやDelphi、.NETのCLRなど)で利用される。その結果タイプライブラリファイルで定義されたコクラスをその言語や環境に持ち込んで使用できるようになる。

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

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

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

以下の例にあるIDLのコクラス定義を考える。 interface ISomeInterface : IUnknown { HRESULT DoSomething();

coclass SomeClass { [default] interface ISomeInterface; }; 上記のコード断片は、ISomeInterfaceというインターフェイスを実装する、SomeClassという名前のCOMクラスを宣言している。

これは概念的には次のようなC++のクラスを定義することに等しい。 struct ISomeInterface : public IUnknown { virtual HRESULT DoSomething() = 0;

class SomeClass : public ISomeInterface { ... }; ISomeInterfaceはC++の純粋仮想基底クラスに相当するものである。

COMのインターフェイスおよびコクラスを含むIDLファイルはタイプライブラリファイルにコンパイルされる。タイプライブラリはCOMクライアントのコンパイル時あるいは実行時に解釈され、オブジェクトがどのインターフェイスをサポートするかを決定し、オブジェクトのインターフェイスメソッドを呼び出すために利用される。

C++では、Windows API関数のひとつであるCoCreateInstance()を使用してCOMオブジェクトをインスタンス化することができる。引数にはクラスID (CLSID) およびインターフェイスID (IID) を指定する。 ISomeInterface* pSomeInterface = NULL; HRESULT hr = CoCreateInstance( CLSID_SomeClass, NULL, CLSCTX_INPROC_SERVER, IID_ISomeInterface, reinterpret_cast<void**>(&pSomeInterface) ); この例では、ISomeInterfaceインターフェイスを実装するSomeClassオブジェクトへのポインタを取得するためにCOMサブシステムが使われる。これは概念的に次のようなC++のコードと等しい。 ISomeInterface* pSomeInterface = new SomeClass();

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

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

システムレジストリを使用する性質上、レジストリ登録されたCOMコンポーネントはコンピュータあるいはネットワーク上のすべてのCOMクライアントから共有される。COMコンポーネントをバージョンアップする際にインターフェイスを変更・追加する場合、旧バージョンをレジストリ登録解除したうえで新バージョンをレジストリ登録し直すか、異なる名前およびGUIDを持つインターフェイスを改めて定義した別のコンポーネントをレジストリ登録する必要がある。前者はバージョンアップの際にCOMクライアントの更新は不要だが、破壊的な仕様変更を避けるなど後方互換性に配慮した設計が求められる。後者は新しいバージョンに対応するCOMクライアントを再作成する必要があるが、複数のバージョンを共存させることができ、バージョン間の互換性を考慮する必要はない。たとえばDirect3Dのバージョン7/8/9/10/11/12は、すべて異なるインターフェイスを持つ独立したCOMコンポーネントDirectX に関してよく寄せられる質問であり、同一コンピュータ上に共存できる。典型的にいって、COMにおける最良のアプローチは、機能を拡張するためには新しいインターフェイスを定義することであるThe Versioning Theory for RPC and COM (Windows)。なお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つの主要な概念をサポートする参照カウント。参照カウントと機能の確認は(オブジェクトの各インタフェースに対してではなく)オブジェクトに対して適用される。従って注意深く実装する必要がある。

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

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

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