コンポーネントオブジェクトモデル(COM)は、マイクロソフトが提供するソフトウェアコンポーネント向けのバイナリインターフェース技術であり、異なるプログラミング言語、プログラミングコンテキスト、プロセス、マシン間で、言語に依存しない方法でオブジェクトを使用することを可能にします。
COM は、 OLE、OLE オートメーション、ActiveX、COM+、DCOMなどの他の Microsoft ドメイン固有のコンポーネント技術、およびDirectX、Windows シェル、UMDF、Windows ランタイム、ブラウザー ヘルパー オブジェクトなどの実装の基盤となっています。
COMは、オブジェクトの内部実装が不明な場合でも、そのインターフェースのみが分かっている場合にオブジェクトを使用できるようにします。コンポーネント実装者は、実装とは別にインターフェースを定義します。
複数のプログラミングコンテキストへの対応は、機能として実装するのが難しい側面をオブジェクトに依存させることで実現されます。オブジェクトの複数回の使用への対応は、各オブジェクトが参照カウントによって自身を破棄することを要求することで実現されます。オブジェクトのインターフェースへのアクセス(型変換と同様)も、各オブジェクトによって提供されます。
COM は、 Microsoft Windowsおよび Apple のCore Foundation 1.3 以降のプラグインアプリケーション プログラミング インターフェイス(API)でのみ利用可能です。[ 1 ]後者は COM インターフェイス全体のサブセットのみを実装しています。[ 2 ]
COMは、時間の経過とともにMicrosoft .NETやWebサービス( WCFなど)といった他のテクノロジーに置き換えられつつあります。しかし、COMオブジェクトはCOM相互運用機能を介して.NET言語で使用できます。
COMは、 SOM、CORBA、Enterprise JavaBeansなどの他のコンポーネント技術と似ていますが、それぞれに長所と短所があります。
C++とは異なり、COMはコンパイラの違いに影響されない安定したアプリケーションバイナリインターフェイス(ABI)を提供します。 [ 3 ]このため、異なるコンパイラでコンパイルされたクライアントによって使用されるオブジェクト指向C++ライブラリにはCOMの使用が有利になります。
1987年に導入されたダイナミックデータ交換(DDE)は、 Windowsにおける最初のプロセス間通信技術の1つでした。[ 4 ] [ 5 ]これにより、アプリケーション間のいわゆる会話でメッセージを送受信することが可能になりました。
COM の設計に携わったトニー・ウィリアムズは、ソフトウェア コンポーネントの概念を取り入れた 2 つの論文をマイクロソフト社内で配布しました。1988年に発表された「オブジェクト アーキテクチャ: 未知のものへの対処 – または – 動的に拡張可能なクラス ライブラリにおける型安全性」[ 6 ]と、1990 年に発表された「継承について: その意味と使用方法」[ 7 ]です。これらは COM の背後にある多くのアイデアの基礎となりました。
オブジェクトリンクと埋め込み(OLE) は、マイクロソフト初のオブジェクトベースのフレームワークであり、DDE をベースに構築され、複合ドキュメント向けに特別に設計されました。1991 年にWordとExcelに搭載されて導入され、その後、1992 年にリリースされた Windows 3.1 以降にも搭載されました。複合ドキュメントの一例として、 Word 文書に埋め込まれたスプレッドシートが挙げられます。Excel のスプレッドシートに変更を加えると、Word 文書にも自動的に反映されます。
1991年、マイクロソフトはVisual Basic 1.0でVisual Basic Extension(VBX)テクノロジーを導入しました。VBXは、ダイナミックリンクライブラリ(DLL)の形式でパッケージ化された拡張機能であり、オブジェクトをフォームにグラフィカルに配置し、プロパティとメソッドで操作することを可能にします。これらは後にVisual C++などの他の言語でも使用できるように適応されました。Windows 3.1はOLE 1.0を統合しました。
1992年、マイクロソフトは新しい基盤オブジェクトモデルであるCOMを搭載したOLE 2をリリースしました。COMアプリケーションバイナリインターフェイス(ABI)は、MAPI ABI(1992年リリース)と同じで、MAPI ABIと同様にMSRPC、そして最終的にはOpen GroupのDCE/RPCに基づいていました。COMは、テキストベースの会話とWindowsメッセージング設計が、アプリケーション機能を堅牢かつ拡張可能な方法で共有できるほど柔軟ではなかったため、DDEを置き換えるために作成されました。COMは識別子としてUUIDを導入しました。
1994年、 COMをベースとしたOLEカスタムコントロール(OCX)テクノロジーがVBXの後継として導入されました。同時に、マイクロソフトはOLE 2が単に「OLE」と呼ばれることを発表した。Windows NT 3.5とWindows 95はOLE 2.0を統合した。[ 8 ]
1996年初頭、マイクロソフトはOCXの新たな用途を発見しました。それは、ウェブブラウザの機能を拡張することでした。マイクロソフトはインターネット関連のOLEの一部をActiveXと改名し、 Microsoft Officeで使用されていた複合ドキュメント技術を除き、すべてのOLE技術を徐々にActiveXに改名していきました。
その後、1996年にマイクロソフトはCOMを拡張し、 DCOMを使用してネットワーク全体で動作するようにした。[ 9 ]
1997年、Windows CEはCOMのサポートを統合した。[ 10 ]
COM IDLは、機能豊富なDCE/RPC IDLをベースに、オブジェクト指向の拡張機能を追加したものです。MicrosoftによるDCE/RPCの実装であるMSRPCは、Windows NTのサービスや内部コンポーネントの主要なプロセス間通信メカニズムとして使用されており、基盤として当然の選択と言えるでしょう。
DCOMは、Windowsデスクトップ上で通信する複数のアプリケーションを持つ単一ユーザーをサポートするだけのCOMの機能から、異なるセキュリティコンテキストで実行され、ネットワーク上の異なるマシン上で動作するオブジェクトをアクティブ化できる機能へと拡張しました。これにより、オブジェクトの作成、アクティブ化、呼び出しを行う権限を持つユーザーの設定、呼び出し元のユーザーの識別、および呼び出しのセキュリティに必要な暗号化の指定といった、必要な機能が追加されました。
マイクロソフトは、分散トランザクション、リソースプーリング、切断されたアプリケーション、イベントの発行と購読、より優れたメモリおよびプロセッサ(スレッド)管理を開発者に提供するとともに、Windowsを他のエンタープライズレベルのオペレーティングシステムの代替として位置づけるために、Windows NT 4でMicrosoft Transaction Server(MTS)を導入しました。
Windows 2000でCOM+と改名されたこの機能セットは、MTSが提供する一連の外部ツールとは異なり、オペレーティングシステムに組み込まれました。同時に、MicrosoftはDCOMを独立したエンティティとして重視しなくなりました。COM+を使用するコンポーネントは、COM+の追加レイヤー、特にオペレーティングシステムのインターセプトサポートによって、より直接的に処理されるようになりました。MTSの最初のリリースでは、インターセプト機能が後付けで追加されました。MTSコンポーネントをインストールすると、コンポーネントを直接呼び出すのではなく、Windowsレジストリが変更されてMTSソフトウェアが呼び出されるようになりました。
Windows 2000には、COM+コンポーネントを構成するためのコンポーネントサービスコントロールパネルのアップデートが含まれていました。
COM+ の利点は、「コンポーネント ファーム」で実行できることでした。コンポーネントのインスタンスは、適切にコーディングされていれば、メモリからアンロードすることなく、初期化ルーチンへの新しい呼び出しでプールして再利用できます。コンポーネントは分散(別のマシンから呼び出し)することもできます。COM+ とMicrosoft Visual Studio は、クライアント側プロキシを簡単に生成するためのツールを提供していたため、リモート呼び出しには DCOM が使用されていましたが、開発者にとっては簡単にできました。COM+ では、COM+ イベントと呼ばれるサブスクライバー/パブリッシャー イベント メカニズムも導入され、キュー コンポーネントと呼ばれるコンポーネントを使用してMSMQ(アプリケーション間非同期メッセージングを提供するテクノロジー)を活用する新しい方法が提供されました。COM+ イベントは、COM+ プログラミング モデルを拡張して、パブリッシャーまたはサブスクライバーとイベント システム間の遅延バインド(遅延バインディングを参照)イベントまたはメソッド呼び出しをサポートします。
.NETは、COMの後継となるマイクロソフトのコンポーネント技術です。.NETはコンポーネント作成の多くの詳細を隠蔽するため、開発が容易になります。
.NETは、一般的に使用されるCOMコントロールのラッパーを提供します。
.NET は名前空間を介して COM+ を活用できSystem.EnterpriseServices、COM+ が提供するサービスのいくつかは .NET にも複製されています。たとえば、System.Transactions名前空間には、COM+ を使用せずにトランザクション管理を提供するクラスが用意されていますTransactionScope。同様に、キューイングされたコンポーネントは、 MSMQトランスポートを備えたWindows Communication Foundation (WCF)に置き換えることができます。
下位互換性のサポートは限定的です。 COM オブジェクトは、ランタイム呼び出し可能ラッパー(RCW) を実装することで .NET で使用できます。[ 11 ]特定のインターフェイス制限に準拠する .NET オブジェクトは、COM 呼び出し可能ラッパー(CCW)を呼び出すことで COM オブジェクトで使用できます。 [ 12 ] COM 側と .NET 側の両方から、もう一方のテクノロジーを使用するオブジェクトはネイティブ オブジェクトとして表示されます。COM相互運用を参照してください。
WCFはCOMのリモート実行における多くの課題を軽減します。例えば、オブジェクトをプロセスやマシン境界を越えて値に基づいて透過的にマーシャリングすることがより容易になります。
Windows Runtime ( WinRT ) は COM ベースの API ですが、拡張版の COM です。COM に似た基盤を持つため、WinRT は複数のプログラミング コンテキストからのインターフェースをサポートしていますが、管理されていないネイティブ API です。API 定義は「.winmd」ファイルに格納され、ECMA 335 メタデータ フォーマットでエンコードされています。これは、 .NET で使用されているCLI メタデータフォーマットとほぼ同じですが、若干の変更が加えられています。このメタデータ フォーマットにより、.NET アプリケーションから WinRT を呼び出す際のオーバーヘッドを P/Invoke よりも大幅に削減できます。
Nano-COMはCOMのサブセットであり、独立してコンパイルされたモジュール/コンポーネント間で関数やメソッドの呼び出しを可能にするCOMのアプリケーションバイナリインターフェース(ABI)の側面に焦点を当てています。Nano-COMは移植性の高いC++ヘッダーファイルで表現できます。Nano-COMは、基盤となる命令アーキテクチャとOSのネイティブABIを拡張して型付きオブジェクト参照をサポートします。一方、一般的なABIは、アトミック型、構造体、配列、および関数呼び出し規約に重点を置いています。
Nano-COMのヘッダーファイルでは、少なくとも3種類のタイプが定義または命名されています。
dynamic_cast<T>新しいインターフェイス型の取得と参照カウントをサポートする抽象仮想関数。shared_ptr<T>Nano-COMの多くの用途では、呼び出し先で割り当てられたメモリバッファを結果として扱うための2つの関数が定義されています。
Direct3DなどのNano-COMの実装の中には、アロケータ関数を使用せず、呼び出し元が割り当てたバッファのみを使用するように制限しているものもある。
Nano-COMには、クラス、アパートメント、マーシャリング、登録などの概念はありません。オブジェクト参照は、関数境界を越えて単純に渡され、標準的な言語構造(C++のnew演算子など)を介して割り当てられます。
Nano-COMは現在、 DirectX / Direct3D / DirectMLの基盤となるABI技術として使用されています。
ActiveX コントロール (COM コンポーネント) はサンドボックス保護のないネイティブ コードとして実行されるため、実行できる内容に制限はほとんどありません。Internet Explorerがサポートしていた ActiveX コンポーネントを Web ページで使用すると、マルウェア感染の問題が発生します。Microsoft は 1996 年にチャールズ フィッツジェラルド氏が「ActiveX が本質的に安全であるとは、最初から主張したことはありません」と述べており、この問題を認識していました。[ 13 ] Internet Explorer の後のバージョンでは、ActiveX コントロールをインストールする前にユーザーに確認を求め、インストールをブロックできるようにしています。
保護レベルの一環として、ActiveX コントロールはデジタル署名によって署名され、真正性が保証されます。
ActiveXコントロールを完全に無効にすることも、選択した一部のコントロールのみを許可することも可能です。
プロセス外COMサーバの透過的なサポートは、プロセス分離の観点からソフトウェアの安全性を高めます。これは、大規模アプリケーションのサブシステムを個別のプロセスに分離する際に役立ちます。プロセス分離により、厳密に定義されたインターフェースを介してのみ通信が行われるため、あるプロセスでの状態破損が他のプロセスの整合性に悪影響を与えるのを防ぎます。したがって、有効な状態を取り戻すには、影響を受けたサブシステムのみを再起動すれば済みます。これは、同一プロセス内のサブシステムには当てはまりません。あるサブシステム内の不正なポインタが、他のサブシステムをランダムに破損させる可能性があるからです。
COM は、 C、C++、Visual Basic、Delphi、Python [ 14 ] [ 15 ]などの複数の言語のバインディングと、いくつかの Windows スクリプト コンテキストを介してサポートされています。コンポーネントへのアクセスは、インターフェイスメソッドを介して行われます。これにより、プロセス内での直接呼び出しと、プロセス間およびコンピュータ間での COM/DCOM サブシステム アクセスが可能になります。
COM クラスであるcoclassは、1 つ以上のインターフェイスを実装します。coclass は、 GUIDであるCLSIDと呼ばれるクラス ID と、 ProgID と呼ばれる人間が読みやすいプログラム識別子によって識別されます。coclass は、これらの識別子のいずれかを使用して作成されます。
各 COMインターフェースは、参照カウントやオブジェクトの他のインターフェースへのアクセスを行うためIUnknownのメソッドを公開するインターフェースを拡張します。これは、型変換(型キャストとも呼ばれる)に似ています。
インターフェースは、インターフェースID(IID)、つまりGUIDによって識別されます。
カスタムインターフェース(から派生したもの)は、インターフェースで宣言された関数を実装する関数へのポインタのリストを含む仮想メソッドテーブルへのポインタIUnknownを介して、早期バインドアクセスを提供します。このリストは、宣言された順序で格納されます。したがって、プロセス内呼び出しのオーバーヘッドは、C++の仮想メソッド呼び出しと同程度になります。
ディスパッチング(遅延バインドアクセスとも呼ばれる)は、を実装することで提供されますIDispatch。ディスパッチングを使用すると、カスタムインターフェースよりも幅広いプログラミングコンテキストからアクセスできます。
多くのオブジェクト指向言語と同様に、COMはインターフェースと実装を分離しています。この分離は、オブジェクトにデフォルトのインターフェースが存在しないCOMにおいて特に顕著です。クライアントは、アクセスするためにインターフェースを要求する必要があります。COMは同じインターフェースの複数の実装をサポートしているため、クライアントは使用するインターフェースの実装を選択できます。
COMタイプライブラリは、コクラスやインターフェースなどのCOMメタデータを定義します。ライブラリは、プログラミング言語に依存しない構文であるインターフェース定義言語(IDL)で定義できます。IDLはC++に似ていますが、インターフェースとコクラスを定義するための追加の構文があります。IDLは、宣言の前に括弧で囲まれた属性を使用して、識別子やパラメータ間の関係などのメタデータを定義することもサポートしています。
IDL ファイルは MIDL コンパイラによってコンパイルされます。C/C++ で使用する場合、MIDL コンパイラは、宣言されたインターフェースのvtblstructに対応する定義を含むヘッダー ファイルと、インターフェースGUIDの宣言を含む C ファイルを生成します。プロキシ モジュールの C++ ソース コードも MIDL コンパイラによって生成できます。このプロキシには、COM 呼び出しをリモート プロシージャ コールに変換して、プロセス外通信用の DCOM を有効にするメソッド スタブが含まれています。
MIDLは、他のツールが他のコンテキストからのアクセスをサポートするために使用できるバイナリ型ライブラリ(TLB)を生成できます。
以下の IDL コードは、 ISomeInterfaceという名前のインターフェースを実装するSomeClassという名前のコクラスを宣言します。
coclass SomeClass { [ default ] interface ISomeInterface ; };これは概念的には、ISomeInterface が純粋仮想クラス、つまり抽象基底クラスである以下の C++ コードと同等です。
class ISomeInterface {}; class SomeClass : public ISomeInterface { };C++では、COMオブジェクトはCLSIDとIIDを引数にとるCOMサブシステムのCoCreateInstance関数を介してインスタンス化されます。SomeClassは次のように作成できます。
ISomeInterface * interface_ptr = NULL ; HRESULT hr = CoCreateInstance ( CLSID_SomeClass , NULL , CLSCTX_ALL , IID_ISomeInterface , ( void ** ) & interface_ptr );COM オブジェクトは、参照カウントを使用してオブジェクトのライフサイクルを管理します。オブジェクトの参照カウントは、クライアントがメソッドIUnknownAddRefとReleaseメソッドを介して制御します。参照カウントがゼロになったときに、COM オブジェクトは自身のメモリを解放する責任があります。一部のプログラミング環境 ( Visual Basicなど) では、オブジェクトの使用を簡素化するために自動参照カウントが提供されています。C++ では、スマートポインタを使用して参照カウント管理を自動化できます。
AddRefとReleaseを呼び出すべきタイミングに関するガイドラインは以下のとおりです。
リモートオブジェクトの場合、すべての参照カウント呼び出しがネットワーク経由で送信されるわけではありません。プロキシはリモートオブジェクトへの参照を1つだけ保持し、独自のローカル参照カウントを維持します。
C++ 開発者の COM 開発を簡素化するために、Microsoft はATL (Active Template Library)を導入しました。ATL は比較的高レベルの COM 開発パラダイムを提供します。また、スマート ポインタ型を提供することで、COM クライアント アプリケーション開発者が参照カウントを直接管理する必要性をなくします。COM 対応の他のライブラリや言語には、Microsoft Foundation Classes、VC Compiler COM Support [ 16 ] 、 VBScript、Visual Basic、ECMAScript ( JavaScript )、Borland Delphiなどがあります。
COMは言語に依存しないバイナリ規格であり、そのバイナリインターフェースにアクセスできるあらゆるプログラミング環境でオブジェクトを使用できる。
COMクライアントソフトウェアは、COMサブシステムの有効化、COMオブジェクトのインスタンス化と参照カウント、およびサポートされているインターフェイスのオブジェクトへのクエリを担当します。
Microsoft Visual C++ コンパイラは、C++ 属性と呼ばれる C ++ 言語の拡張機能をサポートしており、[ 17 ] COM 開発を簡素化し、 C++ で COM サーバーを実装するために必要な定型コードを最小限に抑えるように設計されています。[ 18 ]
当初、タイプライブラリのメタデータはシステムレジストリに保存する必要がありました。COMクライアントは、オブジェクトを作成するためにレジストリ情報を使用していました。
登録不要 (RegFree) COM は、Windows XPで導入され、タイプ ライブラリのメタデータをアセンブリ マニフェストとして、実行可能ファイル内のリソースとして、またはコンポーネントとともにインストールされる別のファイルに保存できるようになりました。[ 19 ]これにより、同じコンポーネントの複数のバージョンを同じコンピュータの異なるディレクトリにインストールできます。また、XCOPY による展開も可能です。[ 20 ]このテクノロジーは EXE COM サーバーのサポートが限定的で[ 21 ] 、 MDAC、MSXML、DirectX、Internet Explorerなどのシステム全体のコンポーネントには使用できません。
アプリケーションの読み込み中、Windows ローダーはマニフェストを検索します。[ 22 ]マニフェストが存在する場合、ローダーはそこから情報をアクティベーション コンテキストに追加します。[ 20 ] COM クラス ファクトリがクラスをインスタンス化しようとすると、まずアクティベーション コンテキストがチェックされ、CLSID の実装が見つかるかどうかが調べられます。検索が失敗した場合にのみ、レジストリがスキャンされます。[ 20 ]
COM オブジェクトは、タイプ ライブラリ情報なしで作成でき、代わりにDLLファイルへのパスと CLSID のみが必要です。クライアントは、CLSID と IID_IClassFactory を使用して COM DLL 関数を使用してファクトリ オブジェクトDllGetClassObjectのインスタンスを作成できます。クライアントは、ファクトリ オブジェクトのインスタンスを使用してインスタンスを作成できます。[ 23 ]これは COM サブシステムが使用するのと同じプロセスです。[ 24 ]このように作成されたオブジェクトが別のオブジェクトを作成する場合、通常の方法 (レジストリまたはマニフェストを使用) で作成します。ただし、内部オブジェクト (まったく登録されない場合もある) を作成し、独自のプライベート 知識を使用して、それらのオブジェクトにインターフェイスへの参照を渡すことができます。CreateInstance
COM オブジェクトは、同一プロセス内(プロセス内)、プロセス境界を越えて(プロセス外)、またはネットワーク経由でリモート(DCOM)で、透過的に作成および使用できます。プロセス外オブジェクトとリモートオブジェクトは、マーシャリングを使用して、プロセス境界またはネットワーク境界を越えてメソッド呼び出しと戻り値をシリアル化します。このマーシャリングはクライアントからは見えないため、クライアントはオブジェクトをローカルのプロセス内オブジェクトであるかのようにアクセスできます。
COM では、スレッド処理はアパートメントと呼ばれる概念によって処理されます。[ 25 ]個々の COM オブジェクトは、シングル スレッドまたはマルチ スレッドのいずれかである 1 つのアパートメントにのみ存在します。COM には、シングル スレッドアパートメント (STA)、マルチ スレッド アパートメント (MTA)、およびスレッド ニュートラル アパートメント(NA) の 3 種類のアパートメントがあります。各アパートメントは、オブジェクトの内部状態を複数のスレッド間で同期できる 1 つのメカニズムを表します。プロセスは複数の COM オブジェクトで構成され、そのうちのいくつかは STA を使用し、その他は MTA を使用する場合があります。COM オブジェクトにアクセスするすべてのスレッドは同様に 1 つのアパートメントに存在します。COM オブジェクトとスレッドのアパートメントの選択は実行時に決定され、変更することはできません。
同じアパートメントに属するスレッドとオブジェクトは、同じスレッドアクセスルールに従います。そのため、同じアパートメント内で行われるメソッド呼び出しは、COM の支援なしに直接実行されます。アパートメントをまたいで行われるメソッド呼び出しは、マーシャリングによって実現されます。これには、プロキシとスタブの使用が必要です。
COMは、.NETのようなより現代的なコンポーネント技術と比較すると、比較的複雑である。
STAが初期化されると、アパート間およびプロセス間のメッセージルーティングに使用される非表示ウィンドウが作成されます。このウィンドウのメッセージキューは定期的に「ポンプ」される必要があります。この構造は「メッセージポンプ」として知られています。以前のバージョンのWindowsでは、これを怠るとシステム全体のデッドロックが発生する可能性がありました。この問題は、実装の一部としてCOMを初期化する一部のWindows APIによって複雑化しており、実装の詳細が「漏洩」する原因となっています。[ 30 ]
COM 内の参照カウントは、2 つ以上のオブジェクトが循環参照されている場合に問題を引き起こす可能性があります。アプリケーション設計では、オブジェクトが孤立しないように、この点を考慮する必要があります。COM の「イベント シンク」モデルを使用すると、オブジェクトにアクティブな参照カウントが残る場合もあります。イベントを発生させるオブジェクトは、イベントに反応するオブジェクトへの参照を必要とするため、後者の参照カウントがゼロになることはありません。参照サイクルは通常、帯域外終了または分割アイデンティティを使用して解消されます。帯域外終了手法では、オブジェクトはメソッドを公開し、そのメソッドが呼び出されると、他のオブジェクトへの参照を強制的に破棄してサイクルを解消します。分割アイデンティティ手法では、単一の実装が 2 つの別々の COM オブジェクト (アイデンティティとも呼ばれる) を公開します。これにより、COM オブジェクト間に弱い参照が作成され、参照サイクルが防止されます。 [ 31 ]
インプロセスCOMコンポーネントはDLLファイルで実装され、登録ではCLSIDごとに1つのバージョンしか登録できないため、状況によっては「DLL地獄」の影響を受ける可能性があります。登録不要のCOM機能は、インプロセスコンポーネントにおけるこの問題を解消しますが、アウトオブプロセスサーバーでは登録不要のCOMは利用できません。
CoGetClassObject 関数の呼び出しで、DLL にロードされるクラス オブジェクトが見つかった場合、CoGetClassObject は DLL のエクスポートされた DllGetClassObject 関数を使用します。