COMの設計が美しい理由の知識マップ(知識グラフ)
COMはWindows上でコンポーネント同士がやり取りするためのバイナリ契約で、すべてのインターフェースが継承するIUnknownがQueryInterfaceによる機能確認とAddRef/Releaseによる参照カウント管理を提供します。CLSIDとIIDというGUIDでコンポーネントとインターフェースを一意に識別することで名前の衝突を避け、既存インターフェースを変えずに新しいインターフェースを追加してQueryInterfaceで確認する設計がバージョン共存を実現します。呼び出し側と同じプロセスに読み込むIn-proc(DLLサーバー)はビット数の一致が必須で相手のクラッシュに道連れになりますが、別プロセスで動くOut-of-proc(LocalServer)はその制約から自由な代わりに、相手が消えたときのRPC_E_DISCONNECTEDのような切断エラーへの備えが必要になります。HRESULTという戻り値による成否表現とProxy/Stub、IDLによる契約の事前定義が、この仕組み全体を言語やプロセスの境界から独立させています。
COM(コンポーネントオブジェクトモデル)
公開インターフェース、レジストリ登録、Apartment Modelなどを土台にした、Windowsのバイナリ互換コンポーネントモデル。
IUnknown
COMのすべてのインターフェースが実装しなければならない基底インターフェースで、QueryInterface・AddRef・Releaseの3メソッドを持つ。
QueryInterface
COMオブジェクトが特定のIIDのインターフェースに対応しているかを、呼び出し側が実行時に安全に確認するための、IUnknownが持つメソッド。
COMの参照カウント(AddRef/Release)
AddRefで増え、Releaseで減って0になった時点でオブジェクト自身を破棄する、IUnknownが提供する寿命管理の仕組み。
CLSID(Class ID)
COMコンポーネントの実装(クラス)を一意に指すGUID。レジストリ上の登録の鍵になる識別子。
IID(インターフェース識別子)
COMインターフェース(契約)を一意に識別するGUID。契約の内容が変われば必ず新しいIIDになる。
HRESULT
COMの呼び出し結果を表す32ビットの符号付き整数のエラーコード体系で、.NETの例外はHRESULTに対応付けられてCOM側へ伝わる。
COMのバイナリ互換性
プログラミング言語やランタイムを問わず、一度ビルドしたCOMコンポーネントをそのまま再利用できる性質。
COMのバージョン共存
既存インターフェースを変更せずに新しいインターフェースを追加し、QueryInterfaceで対応状況を実行時に確認することで後方互換を保つ設計。
In-proc COM(DLLサーバー)
COMコンポーネントを呼び出し側と同じプロセスに読み込んで使う形態。
DLLサーバー
COM LocalServer(別プロセスCOMサーバー)
COMサーバーをEXEとして別プロセスで動かす形態。bitnessの壁を越えたり、クラッシュを隔離したりできる。レジストリのCLSID配下のLocalServer32キーにEXEパスを登録する。
out-of-proc COMサーバー
bitness一致要件
ネイティブモジュール(in-procのCOMコンポーネント、P/Invokeで呼ぶDLL、ベンダー提供SDKのDLLなど)を読み込むホストプロセスと、そのモジュールのビット構成(32bit/64bit)が一致していなければならないという制約。
32bit/64bitの一致
In-procのクラッシュ道連れ
In-proc COMコンポーネントがクラッシュすると、同じプロセスで動作しているホストアプリケーションも一緒に落ちてしまう現象。
Out-of-proc COMの切断エラー
Out-of-proc COMのサーバープロセスがクラッシュ・終了したときに呼び出し側へ返る、RPC_E_DISCONNECTEDやRPC_S_SERVER_UNAVAILABLEといったエラー群。
Proxy/Stub
マーシャリングを担当する一組の部品。呼び出す側に立つProxyと、呼ばれる側で受け取った呼び出しを本物のオブジェクトへ渡すStubからなる。
IDL(インターフェース定義言語)
インターフェースという契約を先に記述し、そこから呼び出し側・実装側両方のヘッダーやコードを生成するための言語。
.NETのCOM相互運用(COM Interop)
ComImport属性やRCW(Runtime Callable Wrapper)を介して.NETからCOMコンポーネントを呼び出す仕組み。HRESULTの失敗を例外に変換するなど、決まった規則で変換する。
ProgID(Programmatic Identifier)
CLSIDに付けた、人が読める文字列の名前。`Excel.Application`のような形でCLSIDに対応付けられる。
ActiveX
実務ではCOMベースのコントロールやその周辺資産をまとめて指す言葉として使われる技術群。特に.ocx形式のUIコントロールやIE/コンテナへ埋め込む部品を含むことが多い。
ActiveXコントロール
すべてのCOMインターフェースはIUnknownを継承し、QueryInterface・AddRef・Releaseの3メソッドを備える契約になっている。
実行時に別のインターフェースへの対応を尋ねるQueryInterfaceが、実装を知らずに機能を確認する手段になっている。
AddRefで増え、Releaseで減って0になると破棄される参照カウントで、COMオブジェクトの寿命を管理する。
コンポーネントの実装はCLSIDという世界で一意なGUIDで識別される。
インターフェースの契約もIIDというGUIDで一意に識別され、名前の衝突を防ぐ。
COMメソッドは例外ではなく整数のHRESULTで成否を返し、これが言語をまたいだ解釈を可能にする。
仮想関数テーブルの並び順だけを規約にすることで、言語やコンパイラを問わない再利用を実現している。
呼び出しの並び順がバイナリレベルで決まるのは、すべてのインターフェースがIUnknownを継承する規約があるからである。
既存インターフェースを変えずに新しいインターフェースを追加していく設計が、後方互換を保ったバージョン共存を実現する。
新しい版の契約に対応しているかどうかを、QueryInterfaceで実行時に確認する。
コンポーネントをそう登録した場合、呼び出し側と同じプロセスにDLLとして読み込むIn-proc(DLLサーバー)という配置形態を持つ。
コンポーネントをそう登録した場合、別プロセスのEXEとして起動するOut-of-proc(EXEサーバー、LocalServer)という配置形態も持つ。
In-procは呼び出し側プロセスとビット数(32bit/64bit)が一致していないと読み込めない。
同じプロセスで動くIn-procコンポーネントがクラッシュすると、呼び出し側のホストプロセスも道連れで落ちる。
別プロセスで動くOut-of-procなら、サーバー側がクラッシュしても呼び出し側は生き残る。
別プロセスであることは相手がいつでも消えうることも意味し、RPC_E_DISCONNECTEDやRPC_S_SERVER_UNAVAILABLEといったエラーが返ることがある。
別プロセスや別マシンの呼び出しでも、Proxy/Stubが引数の詰め直し(マーシャリング)とプロセス間通信を隠し、呼び出し側のコードの見た目を変えない。
インターフェースという契約は通常IDLで先に定義し、そこから各言語向けのヘッダーを生成する。
ProgIDを使って解決する場合、Type.GetTypeFromProgIDのように、人が読めるProgIDから対応するCLSIDのコンポーネントを解決して呼び出せる。
C#でのインターフェースへのキャストは、内部的にQueryInterfaceの呼び出しに相当する。
失敗したHRESULTは.NET側で例外に変換されるという決まった規則で扱われる。
AddRef/Releaseは.NET側では見えなくなるが、RCWという薄いラッパーが代わりに呼び出している。
ActiveXは、実務ではCOMベースのコントロールやその周辺資産をまとめて指す言葉として使われる。