COMってなんだ

COM (Common Object Model) について調べたり実験したりして分かったことを書きます。
想像で書いている箇所もあり、間違いも多くあると思いますのでご注意ください。
調べれば調べるほどどこまでも奥深く沼だなーという気分になりました。

以下は、x64 PC 前提で書きます。

COM ってなんだ

  • インターフェースと実装の分離
  • COM はインターフェイスベースの開発を前提に作られている環境
  • COM はプログラミング言語に依存しないフレームワーク
  • インターフェース定義に専用言語 (IDL) を使う。真の COM プログラマは IDL から作業を開始する。
    • 専用言語でかかれたインターフェイスは、実装に使われるプログラミング言語を想定しない
    • 異なるプログラミング言語で作ったプログラムが相互に連携できる
    • 連携の部分は OS の COM がサポートしてくれる
    • IDL では COM クラスを coclass キーワードで定義する
    • coclass に設定された GUID は COM クラスを識別する。クライアントはこの GUID を利用して COM クラスの実装を起動する
  • COM ではそれぞれの GUID に固有の名前を付けている
    • IID (Interface-ID) - インターフェース属性の GUID
    • CLSID - coclass の GUID
    • LIBID - library の GUID
  • IUnknown インターフェイスの QueryInterface というメソッドで、COM オブジェクトがある特定のインターフェイスを実装しているかどうかを調べることができる
  • インターフェイス絶対に変更できないので、 バージョンアップするにはインターフェイスを新しく定義することになる

COM 開発の流れ

IDL でインターフェース・COMクラスを定義する

  1. IDL ファイル *.idlインターフェイス、COM クラスを定義(どのクラスがどのインターフェイスを持つか定義)する。
  2. ILD を開発環境に取り込むには、Visual Studio や Platform SDK に付属する IDL コンパイラ MIDL.exe で IDL ファイルをビルドする。COM サーバ、COM クラインアントを C++ 以外の言語で開発する場合は、IDL ファイルで library 属性を設定し、IDL コンパイラでビルドした時にタイプライブラリファイルが出力されるようにする。タイプライブラリは IDL をバイナリトークンにしたもの。

COM サーバ、COM クライアントを C++ で実装する場合

  1. IDL コンパイラで生成された *.h, *.c ファイルを使ってインターフェイスを実装する。
  2. ビルドして COM DLL を作る。

COM サーバ、COM クライアントを C++ 以外の言語で実装する場合

COM サーバの実装

  1. 参照にタイプライブラリを追加する。
  2. インターフェイスを実装する。
  3. ビルドして COM DLL を作る。

COM クライアントの実装

  1. VB の場合は CreateObject() の引数に ProgID を指定する。COM の GUID は長くて使いにくいので COM 利用側は プログラム識別子 (ProgID) という別名を利用する。
  2. ProgID がレジストリなどを利用して CLSID に変換され、CLSID を利用して DLL がロードされ、COM オブジェクトが生成される。
  3. COM オブジェクトを利用してクライアント側を実装する。

COM コンポートをレジストリに登録する

COMクライアントからCOMコンポーネント(COMサーバー)を利用するには事前にレジストリに登録が必要です。レジストリへの登録となるので管理者権限での実行が必要です。

COM がアンマネージコードで作成されている場合
[32bit COM の場合]

C:\Windows\SysWOW64\regsvr32 <COM コンポーネント>

[64bit COM の場合]

C:\Windows\System32\regsvr32 <COM コンポーネント>

regsvr32.exe を実行すると COM コンポーネントがロードされ、DllRegisterServer() 関数が呼び出される。通常、この関数の中にレジストリへの登録処理が書かれている。登録を解除するには regsvr32 /u <COM コンポーネント> を実行する。この場合は、DllUnregisterServer() 関数が呼ばれるのでこの中にレジストリの登録を解除する処理が書かれている。

COM がマネージコードで作成されている場合

[32bit COM の場合]

C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm <COM コンポーネント>

[64bit COM の場合]

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm <COM コンポーネント>

.Net から COMコンポーネントを利用する

  • 参照に *.tlb (Type Library) を追加します。事前にレジストリに COM コンポーネントが登録されていないとエラーになります(実験結果からこう理解していますが怪しいです・・・)
  • 参照を追加するとメタデータを含む 相互運用機能アセンブリ(Interop Assembly) が自動生成されます。Tlbimp.exe ツールが生成している?

    COM オブジェクトのメンバーへの参照は相互運用アセンブリにルーティングされてから、実際の COM オブジェクトに転送されます。 COM オブジェクトからの応答は相互運用アセンブリにルーティングされてから、.NET Framework アプリケーションに転送されます。
    COM相互運用

COM サーバー

  • COM サーバーには インプロセスサーバーアウトオブプロセスサーバー の 2種類がある。
  • インプロセスサーバーは DLL として実装され、クライアントと同じプロセス空間で実行される。そのためクライアントと bitness は同じである必要がある。クライアントは、COM インターフェースへの直接呼び出しを使ってインプロセスサーバーと通信する。
  • アウトオブプロセスサーバーは EXE として実装される。ローカルコンピューターまたはリモートコンピューター上に存在できる。別プロセスとの通信なので bitness は異なっていても問題ない。

64-bit OS で 32-bit COM を使う方法

リモートプロシージャコール (RPC) は問題なく 32-bit と 64-bit の境界を越えることができるため、COM コンポーネントをアウトオブプロセスで利用している場合は、64-bit Windows に移行するときに問題を引き起こすことはない。

COM コンポーネントをインプロセスからアウトオブプロセスに移行する方法

方法1:プロジェクトタイプをインプロセスからアウトオブプロセスに変換する

  • 関連するソースコードが手元にある必要がある
  • Visual Studio のプロジェクトの設定でサーバーのタイプを DLL から EXE に変更してビルドする
  • この方法の利点は 64-bit クライアント側は全く変更する必要がないこと

方法2: COM+ を利用する

方法3: dllhost をサロゲートホストとして使う

  • dllhost は 32-bit クライアントと 64-bit DLL の間の呼び出しを中継するラッパーとして機能する
  • レジストリ設定が必要

方法4: アプトオブプロセスの COM Wrapper を DLL に追加する

参考サイト

よさそうな本 (中古だけど)