全般 Edit

なぜCOMが必要なのか Edit

COM通信は大きく分けて以下の3つが挙げられる。

  1. 同一プロセス内でのCOM通信。DLL形式のCOMサーバを用いる。
  2. 異なるプロセス間でのCOM通信。マーシャリングという仕組みによって実現される。EXE形式のCOMサーバを用いる。
  3. 異なるマシン間でのCOM通信。ネットワークを介して通信される。DCOMと呼ばれる。

このうち、2番目と3番目の通信方法を実現するためにはCOMは有効である。しかし、1番目の同一プロセス内での通信は、COMではない通常のDLLでも実現可能である。
それでもCOM通信を用いる理由は、通常のDLLは使用する際に以下の制約があるからである。

  • DLLを使用するアプリケーション側とDLL側で、関数の呼出規約を合わせる必要がある。
    しかし、言語によっては呼出規約を自由に決められない場合がある。
  • DLLを使用するアプリケーション側と、DLL側で、関数のマネージド/アンマネージドを合わせる必要がある。
    しかし、言語によっては.NETフレームワークを使用できず、マネージドコードを作れない場合がある。

関連用語 Edit

呼称説明
COM
(Component Object Model)
アプリケーションの機能の異なる言語間での連携、再利用を行う技術。
COMサーバ決められたインタフェースでCOMクライアントに機能を提供するCOMコンポーネント。
COMクライアントCOMサーバの提供する機能を利用するCOMコンポーネント。
カスタムインタフェースCOMがアーリーバインディングでインタフェースを提供するための手段。
IUnknownを継承している必要がある。
ディスパッチインタフェース
(またはオートメーション)
COMがレイトバインディングでインタフェースを提供するための手段。
IDispathを継承している必要がある。(必然的にIUnknownも継承される)
引数は全てVARIANT型で授受されるため、VBやJSなどをクライアントにすることが可能となる。
デュアルインタフェースカスタムインタフェースとディスパッチインタフェースの両方を持つこと。
(全てのCOMコンポーネントはカスタムインタフェースを提供するため、ディスパッチインタフェースを提供するCOMコンポーネントはデュアルインタフェースである)
ATL
(Active Template Library)
COMオブジェクトの開発を支援するための、マイクロソフトによるクラスライブラリ。
MFCを用いるより軽量なコンポーネントを作成することを目的としている。
ActiveXCOMを利用した、自己登録可能なコントロールのこと。
MFC付属のコントロールは全てActiveXコントロール。
OLE
(Object Linking and Embedding)
オブジェクトの埋め込み技術。
OLEコントロールとはActiveXコントロールの前身の存在で、名称が異なるだけで同じもの。
OCX
(OLE-based Control eXtension)
1つ以上のActiveXコントロールが登録、保存されたファイル。
ファイルの拡張子はOCXだが、中身はDLL。
UUID
(Universally Unique Identifier)
汎用一意識別子。128ビットの数値で表わされるユニークな識別子。
GUID
(Globally Unique Identifier)
グローバル一意識別子。マイクロソフトにより実装されたUUIDの一種。
LIBIDライブラリID。COMコンポーネントのライブラリを識別するためのGUID。
CLSIDクラスID。COMコンポーネントのコクラスを識別するためのGUID。
IIDインタフェースID。COMコンポーネントのインタフェースを識別するためのGUID。
IDL
(Interface Definition Language)
インタフェース記述言語。
言語に依存しないオブジェクトのインタフェースを記述する言語。
IDLファイル(COMクライアント側)、ODLファイル(COMサーバ側)などで使用される。OMG IDL。
参照カウントCOMコンポーネントが複数のクライアントから同時に使用されることを考慮して生存期間を管理するための手段。
あるCOMコンポーネントがクライアントから使用開始されたときにインクリメントされ、そのクラインアントが使用終了したときにデクリメントされる。
参照カウントが0→1になるときにCOMコンポーネントのプロセスが起動し、1→0になるときにプロセスが終了する。
アパートメント異なるプロセス/スレッド間であってもそれを意識せずにCOM通信を行うためのCOM特有の考え方。STAとMTAが存在する。
STAのCOMサーバーは、必ずシングルスレッドで動作し、MTAの場合はマルチスレッドで動作する。
COMを使用するスレッドは、サーバー側クライアント側を問わず、CoInitializeEx()によって必ずいずれかのアパートメントに属する。
マーシャリング異なるアパートメント間(つまり、異なるプロセスやスレッド間)であってもCOM通信を関数コールのように実現するための方法。
プロキシ/スタブDLLという仲介役によって実現される。
接続ポイント

参考リンク Edit

http://msdn.microsoft.com/ja-jp/vstudio/ffd7bcba-8a24-41c8-8f86-92c55c65fbde.aspx
http://program.station.ez-net.jp/special/vc/atl-com/variant.asp
http://www.tomosan.org/dev/vs/activex.html
http://www.ops.dti.ne.jp/~allergy/com/com.html
http://atata.sakura.ne.jp/

注意事項 Edit

  • ATLによるCOMのレジストリ登録で、パスに2バイト文字が使われていると正常に登録できない。→リンク

データ型 Edit

Variant型 Edit

様々なデータ型を格納するために、保持するデータ型のIDと実際のデータを共用体で格納する。

 

関連する関数等。

種類名称処理
関数VariantInit()Variant型変数の初期化
VariantClear()Variant型変数のクリア
VariantCopy()Variant型変数のコピー
VariantCopyInd()???
VariantChangeType()Variant型の保持するデータ型の変換
VariantChangeTypeEx()???
変数型VARTYPEVARENUM列挙体の値を格納してVariant型の種類を表す
構造体VARIANTVariant型を扱うための構造体
列挙体VARENUM保持できるデータ型の列挙体
MFC クラスCOleVariantVARIANT構造体のラッパクラス
ATL クラスCComVariantVARIANT構造体のラッパクラス
その他 クラス_variant_tVARIANT構造体のラッパクラス
※...\VC98\Include\OAIDL.h(338) を参照。
 

(C++からVARIANT型の変数を渡す場合)

  1. VARIANT型変数を、VariantInit()で初期化。
  2. VARIANT::vt に VARENUM列挙体 からデータ型を代入。
  3. データ型に合った共用体のメンバ変数に値を代入。
  4. 値を渡す。
  5. VARIANT型変数を、VariantClear()でクリア。

(C++でVARIANT型の変数を受け取る場合)

  1. VARIANT型変数を、VariantInit()で初期化。
  2. 値を受け取る。
  3. データ型に合った共用体のメンバ変数から直接値をコピーしたり、VariantChangeType()でデータを変換することで操作可能。

BSTR型 Edit

実体は wchar_t*型のUnicode文字列。ただし、通常の文字列とは異なる仕様を持つ。

  • 先頭から前4バイト分が文字数を表す。
  • 文字列中にNULL文字を埋め込むことができる。逆にいえば、NULL文字が文字列の終端を表さない。
 

関連する関数等。

種類名称処理
関数SysAllocString()引数で指定した文字数のBSTRを作成する
SysFreeString()引数で指定したBSTRを削除する
SysStringLen()引数で指定したBSTRの文字数を返す
SysStringByteLen()引数で指定したBSTRのバイト数を返す
SysAllocStringByteLen()引数で指定した8ビット文字列を指定したバイト数コピーしたBSTRを作成する
SysAllocStringLen()引数で指定した16ビット文字列を指定した文字数コピーしたBSTRを作成する
MFCCString::AllocSysString格納されている文字列を元にBSTRを作成する
ATL クラスCComBSTR文字数を表す4バイトのプリフィックスを内包したBSTR型のラッパクラス
その他 クラス_bstr_tBSTR型のラッパクラス
 

メモリの扱いについて。→参考

  • 入力値(引数)にBSTR型を持つメソッド
    BSTRのメモリ確保、解放はクライアント(呼び出し)側が行う。
  • 出力値(引数、戻り値)にBSTR型を持つメソッド
    BSTRのメモリ確保はサーバ(メソッド実装)側が、解放はクライアント(呼び出し)側が行う。(クライアントがVBであれば、string型を使うことでスコープを抜けるときに自動的に解放されるはず?)

BSTR型を扱うときは、メモリリークしないように注意する。
以下は、_bstr_tクラスの扱いによるメモリリークの有無の例。

  • メモリリークしない例
      1
      2
      3
      4
      5
      6
      7
      8
      9
    
        BSTR    bstr = NULL;
     
        // COMのメソッドを呼び出す
        m_pTestCom->GetString(&bstr);
     
        // コンストラクタを使用して格納(第2引数をfalseにすることで、bstr自身にアタッチする)
        _bstr_t bstrt(bstr, false);
     
        // _bstr_tクラスのデストラクタで、bstrが解放される
    
  • メモリリークする例
      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
    
        BSTR    bstr = NULL;
        _bstr_t bstrt;
     
        // COMのメソッドを呼び出す
        m_pTestCom->GetString(&bstr);
     
        // =演算子を使用する(bstrのコピーが格納される)
        bstrt = bstr;
     
        // _bstr_tクラスのデストラクタではコピーした文字列の解放を行うため、
        // 元のbstrは解放されないまま残ってしまう
    

配列型 Edit

次元数や要素数と、データを指すポインタを持つ構造体。

 

関連する関数等。

種類名称処理
構造体SAFEARRAYCOMの配列型を管理する構造体
MFC クラスCOleSafeArrayVARIANT構造体のラッパクラス
ATL クラスCComSafeArrayVARIANT構造体のラッパクラス
VC++6.0では扱えない

HRESULT型 Edit

VARIANT_BOOL型 Edit

COMで使用する真理値型。C言語で使用するBOOL型やbool型とは値が異なり、VBの真理値型と同じ値となる。

  • 真(VARIANT_TRUE) : -1
  • 偽(VARIANT_FALSE) : 0

スマートポインタ Edit

COMオブジェクトは使い終わったら必ず解放しなければならないが、スマートポインタを使用するとスコープを抜けるときに自動的にCOMオブジェクトの解放を行ってくれる。

 

関連する関数等。

種類名称処理
ATL クラスCComPtr<T>スマートポインタを実現するためのCOMオブジェクト管理用のテンプレートクラス
その他 クラス_com_ptr_t<T>スマートポインタを実現するためのCOMオブジェクト管理用のテンプレートクラス
 

オブジェクトの生成 Edit

CComPtr<T> はテンプレートクラスとして定義されているため、生成したいオブジェクトでポインタを定義する。

  1
    CComPtr<ISampleObject> pSampleObj;

実際の生成には、CComPtr<T>::CoCreateInstance() を用いる。

オブジェクトの代入 Edit

CComPtr<T>型の変数を定義し、既に生成済みのCOMオブジェクトを代入するときには、「=」演算子を使用してはいけない。「=」演算子はオーバロードされており、COMオブジェクトの参照カウントがインクリメントされてしまう。
代入には、基底クラスのCComPtrBase<T>::Attach() を用いる

  1
  2
  3
    // pIFooObj はスマートポインタではなく、ISampleObject型のポインタ
    pISampleObj = pIFooObj;         // ×
    pISampleObj.Attach(pIFooObj);   // ○

また、CComPtr<T>オブジェクトとして生成済みのCOMオブジェクトのアドレスを別の変数や関数に渡すときは、メンバ変数 CComPtr<T>::p を用いる。

オブジェクトの関数の呼び出し Edit

オーバロードされた演算子「->」を用いる。つまり、スマートポインタであることを意識せずに関数呼び出しを行うことができる。

  1
  2
    // 通常のCOMオブジェクトのように使用できる
    pISampleObj->Func();

オブジェクトの解放 Edit

何もする必要はない。デストラクタで解放を行ってくれるため、スコープを抜けるときに自動的にCOMオブジェクトが解放される。
もし明示的にCOMオブジェクトの解放を行う場合は、内部のオブジェクトを直接解放するのではなく、CComPtr<T>::Release() を用いる。「->」演算子を使用してはいけないことに注意。

  1
  2
    pISampleObj->Release();     // ×
    pISampleObj.Release();      // ○

また、スコープを抜けるときに自動的に解放されたくない場合は、基底クラスのCComPtrBase<T>::Detach() を用いてCOMオブジェクトをスマートポインタから切り離す。

COM識別子(GUID) Edit

COMサーバを識別するために、GUIDという128ビットの値を用いる。
GUIDは5グループの16進文字列で表わされる。それぞれ、8, 4, 4, 4, 12文字。

  1
  2
  3
  4
  5
  6
  7
  8
    // GUIDの例
    AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE
 
    // GUID構造体
    GUID::Data1    (DWORD) // AAAAAAAA
    GUID::Data2    (WORD)  // BBBB
    GUID::Data3    (WORD)  // CCCC
    GUID::Data4[8] (BYTE)  // DDDD-EEEEEEEEEEEE
 

ProgID Edit

COMサーバを識別するための文字列。VB系言語で CreateObject() に引数として渡す文字列。内部的には対応するCLSIDに変換される。

CLSID Edit

COMクラスの識別子で、GUIDで表わされる。
GUID型にtypedefされている。また、「REFCLSID」という識別子が、CLSID型の参照型として定義されている。(C言語ではポインタ型)

IID Edit

COMクラス内のインタフェースの識別子で、GUIDで表わされる。
GUID型にtypedefされている。また、「REFIID」という識別子が、CLSID型の参照型として定義されている。(C言語ではポインタ型)

その他 Edit

  • FMTID

    プロパティセットフォーマットの識別子で、GUIDで表わされる。(typedefされている。)

  • CATID

    コンポーネントカテゴリの識別子で、GUIDで表わされる。(typedefされている。)

インタフェース定義ファイルの生成の流れ Edit

サーバもクライアントも、使用するインタフェースの定義はIDLファイルやタイプライブラリを用いる。通常、インタフェースの作成者が用意して、サーバもクライアントも同じ定義を使用する。
C++で実装を行う場合、インタフェースの定義をヘッダファイルやソースファイルに置き換える必要がある。

以下にファイルの生成の流れを示す。
File not found: "ref001.PNG" at page "技術資料/COM"[添付]

 

デバッグのヒント Edit

  • atlbase.hをインクルードする前に_ATL_DEBUG_INTERFACESマクロを定義しておくことで、COMの参照カウントの増減をトレースできる。(デバッグウィンドウに表示される。)
      1
      2
      3
    
    // ATLの使用開始をウィザードで行った場合、stdafx.hでatlbase.hがインクルードされているはず
    #define		_ATL_DEBUG_INTERFACES
    #include	<atlbase.h>

#importディレクティブのオプション Edit

オプション説明
named_guidsこのオプションを使用すると、LIBID,CLSID,IID,DIID等の定義がGUID構造体型の変数定義として追加される。
(これらの変数は、::CoCreateInstance 関数の第1引数,第4引数に用いることができる。)
raw_interfaces_onlyデフォルトでは下記の動作。
 ・非プロパティのメソッドに対してラッパ関数の定義と実装が作成される。
 ・もとのメソッドは"raw_"を冠する名称に変更される。
このオプションを使用すると、上記の処理が抑制される。→tliファイルが作成されない。
raw_native_typesデフォルトでは下記の動作。
 ・ラッパ関数に対して、COM特有の型であるBSTR型などの替わりにラッパクラスを使用した定義が作成される。
このオプションを使用すると、上記の処理が抑制される。
※raw_interfaces_only オプションと併用する場合はそもそもラッパ関数が作成されないため、効果が表れない。

COMクライアント Edit

COMコンポーネントをバインドする2種類の方法 Edit

アーリーバインディングとレイトバインディングの2種類の方法が存在する。

アーリーバインディングレイトバインディング
別名事前バインディング実行時バインディング、遅延バインディング
概要必要なインタフェースのインスタンスを取得し、直接メソッド等をコールする方法。IDispatchインタフェースのインスタンスを取得し、間接的にメソッド等をコールする方法。
COMコンポ―ネント側で実装する必要があるインタフェースカスタムインタフェース
(IUnknownインタフェース)
デュアルインタフェース
(IDispatchインタフェース)
処理速度速い遅い
COMコンポ―ネントの仕様変更時の影響影響を受けにくい影響を受けやすい
各言語別の使用方法C++直接メソッド等をコールする
ラッパクラスを作ることが多い
IDispatch::GetIdsOfNames()でメソッド名からdispIDを取得し、IDispatch::Invoke()を介してメソッド等をコールする
VB参照設定で予めプロジェクトに取り込む
変数はそのインタフェース自体の型で宣言して使用する。
※VBSでは使用不可
変数をObject型で定義しておき、CreateObject()で実行時にインタフェースを取得する
C#???リフレクションを用いる?
JavaJAVA-COMブリッジという技術を用いる。JCOM等のツールを使用?詳細不明。

実装手順 Edit

  1. COMの使用開始

    スレッド内でCOMを使う前に、::CoInitialize() をコールする。

  2. COMオブジェクトの定義、生成
  3. COMオブジェクトの使用
  4. COMオブジェクトの解放
  5. COMの使用終了

    スレッド内でCOMを使い終えたら、::CoUninitialize() をコールする。
    使用したインタフェースの解放時には参照カウントがデクリメントされるだけで、実際にプロセスが削除されるのはこのタイミング?

COMサーバ(COMコンポーネント) Edit

レジストリ登録 Edit

COMコンポーネントは、レジストリに自分のパスとGUIDを登録しておく必要がある。

DLL形式のCOMサーバ Edit

  • 登録
    regsvr32 xxx.dll
  • 解除
    regsvr32 /u xxx.dll

EXE形式のCOMサーバ Edit

  • 登録
    xxx.exe /RegServer
  • 解除
    xxx.exe /UnregServer

タイプライブラリのバイナリへの埋め込み Edit

VisualStudioでCOMサーバを作成すると、リソースにCLSIDをレジストリ登録するためのレジストリスクリプトが埋め込まれるが、それだけではIIDの登録は行われない。IIDの登録には、COMサーバのビルド時に作られるtlbファイルが必要になる。

tlbファイルは、レジストリ登録時にCOMサーバと同じ場所に置いておくと認識される。また、リソースにtlbファイルを指定することでexeやdllにtlb情報を埋め込むこともできる。その場合はtlbファイルは必要ない。(DLL形式のCOMサーバの場合は、自動的にtlbを埋め込むようにリソースが作られているはず)

リソースへtlbファイルを埋め込むには、rcファイルの「3 TEXTINCLUDE DISCARDABLE」ブロックの末尾に相対パスでtibファイルを指定するコードを追加する。

3 TEXTINCLUDE DISCARDABLE 
BEGIN
    :
    "#ifdef _DEBUG\r\n"
    "1 TYPELIB ""Debug\\xxx.tlb""\r\n"
    "#else\r\n"
    "1 TYPELIB ""Release\\xxx.tlb""\r\n"
    "#endif    // _DEBUG\r\n"
    "\0"
END

※tlbファイルは相対パスを指定する必要があるが、EXE形式のCOMサーバの場合はDebug/Releaseそれぞれのフォルダでtlbファイルが作成されるため、条件分岐させた。

参考リンク1 参考リンク2

VBアプリケーションによる自動登録 Edit

VBで作成されたアプリケーションは、自身が参照するOCXがレジストリに見つからない場合、起動時にエラーとなる場合がある。しかし、このときDLL/OCXの検索順序?に従ってCOMコンポーネントを検索して見つかると、自動的にレジストリ登録(regsvr32)するようである。そのため、次回からは起動時にエラーとならずアプリケーションを使用することができる。

実装すべきインタフェース Edit

  • IUnknownインタフェース

    全てのCOMコンポーネントは、IUnknownインタフェースを実装する必要がある。

  • IDispatchインタフェース

    デュアルインタフェースに対応する場合は、IDispatchインタフェースを実装する必要がある。
    ※IDispatchインタフェースは、IUnknownインタフェースを含む。

実装すべき機能 Edit

レジストリへの自己登録機能など、必要な機能を実装する。

DLL形式のCOMサーバ Edit

以下の4つの関数をエクスポートする必要がある。

  • xxx
  • xxx
  • xxx
  • xxx

EXE形式のCOMサーバ Edit

以下のコマンドラインオプションに対応する必要がある。

  • /RegServer
  • /UnRegServer
  • /Embedding
  • /Automation

不具合/注意点 Edit

COMサーバ(EXE形式)にウィザードによって追加されるコード Edit

VC++6.0でウィザードによって追加されるコードが誤っている箇所があるので注意。
COMサーバ(EXE形式)にウィザードによって追加されるコード

STAでの再入 Edit

COMサーバーをSTAにしておくことで、マルチスレッドで動作することはなくなる。しかし、別のCOM呼び出しをしている最中に処理が再入されることがあるので注意が必要。
(STAではシングルスレッドで動作させるために、COM呼び出しされたことをウィンドウメッセージとしてキューに溜めている。自身が別のCOM呼び出しを行っている間はメッセージループが回るため、処理が再入される。もちろん、通常のウィンドウメッセージの動作も再入されるということになる。)
参考:http://blogs.wankuma.com/melt/archive/2007/12/13/112869.aspx

SendMessage中のCOM呼び出し Edit

SendMessageによって別スレッドから送信されたメッセージを処理している間は、EXE(アウトプロセス)のCOMサーバーに対してのCOM呼び出しは失敗して例外が発生する。(デッドロックを防ぐため?)COM呼び出しを行う前に、::InSendMessage() でチェックをすることで防げる。
参考:http://support.microsoft.com/kb/131056/en-us


添付ファイル: fileref001.png 7件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-05-26 (土) 13:35:47 (2180d)