相互運用性を得るための COM コンポーネントの作成

更新 : 2007 年 11 月

将来、COM ベースのアプリケーションを作成しようと計画している場合は、マネージ コードと効率的に相互運用できるようにコードをデザインできます。高度な計画に基づいて、アンマネージ コードからマネージ コードへの変換を単純化することもできます。

マネージ コードと対話する COM 型を作成するための推奨される手順の概要を次に示します。

タイプ ライブラリの提供

ほとんどの場合、共通言語ランタイムでは、COM 型を含め、すべての型についてメタデータが必要です。タイプ ライブラリ インポータ (Tlbimp.exe) は、Windows Software Development Kit (SDK) に含まれており、COM タイプ ライブラリを .NET Framework メタデータに変換できます。タイプ ライブラリがメタデータに変換されたら、マネージ クライアントは COM 型をシームレスに呼び出すことができます。使いやすくするために、タイプ ライブラリには常に型情報を提供します。

タイプ ライブラリは、個別のファイルとしてパックすることも、.dll、.exe、.ocx の各ファイル内のリソースとして埋め込むこともできます。さらに、メタデータを直接生成することもでき、それによって発行元のキー ペアでメタデータに署名できます。キーで署名されたメタデータは、最も信頼できるソースを持ち、呼び出し元が誤ったキーでバインドしないようにするために役立ちます。したがって、セキュリティも強化されます。

タイプ ライブラリの登録

ランタイムは、呼び出しを正しくマーシャリングするために、特定の型を記述するタイプ ライブラリの検索が必要となる場合があります。ランタイムでタイプ ライブラリを確認できるようにするには、遅延バインディングの場合を除いて、そのタイプ ライブラリを登録しておく必要があります。

regkind フラグを REGKIND_REGISTER に設定して Microsoft Win32 API LoadTypeLibEx 関数を呼び出すことによって、タイプ ライブラリを登録できます。Regsvr32.exe は、.dll ファイルに埋め込まれているタイプ ライブラリを自動的に登録します。

可変長配列の代用としてのセーフ配列の使用

COM セーフ配列は、自己記述型の配列です。セーフ配列を調べることによって、ランタイム マーシャラはランク、サイズ、範囲、および通常は実行時の配列の内容の型を判別できます。可変長または C スタイルの配列は、自己記述型の配列と同じ機能は持っていません。たとえば、次のアンマネージ メソッド シグネチャは、配列パラメータに関する情報は要素の型しか提供しません。

HRESULT DoSomething(int cb, [in] byte buf[]);

実際に、この配列は、参照渡しされるそれ以外のパラメータと見分けが付きません。その結果、Tlbimp.exe は DoSomething メソッドの配列パラメータを変換しません。代わりに、この配列は、次のコードに示すように、Byte 型への参照として表されます。

Public Sub DoSomething(cb As Integer, ByRef buf As Byte)
public void DoSomething(int cb, ref Byte buf);

相互運用性を高めるために、引数を SAFEARRAY としてアンマネージ メソッド シグネチャに入力できます。たとえば、次のようにします。

HRESULT DoSomething(SAFEARRAY(byte)buf);

Tlbimp.exe は、SAFEARRAY を次のマネージ配列型に変換します。

Public Sub DoSomething(buf As Byte())
public void DoSomething(Byte[] buf);

オートメーション準拠のデータ型の使用

ランタイム マーシャリング サービスは、オートメーション準拠のデータ型すべてを自動的にサポートします。オートメーションに準拠しない型は、サポートされる場合と、サポートされない場合があります。

タイプ ライブラリでのバージョンおよびロケールの提供

タイプ ライブラリをインポートする場合、アセンブリに対してタイプ ライブラリのバージョンおよびロケールも反映されます。その後、マネージ クライアントは、アセンブリの特定のバージョンやロケール、またはアセンブリの最新のバージョンにバインドできます。タイプ ライブラリにバージョン情報を提供することによって、クライアントは使用するアセンブリのバージョンを正確に選択できます。

blittable 型の使用

データ型は blittable 型または非 blittable 型のいずれかです。blittable 型には、相互運用の境界にまたがる共通の表現があります。整数と浮動小数点型は blittable 型です。blittable 型の配列および構造体も blittable 型です。文字列、日付、およびオブジェクトは、マーシャリング プロセス時に変換される非 blittable 型の例です。

blittable 型も非 blittable 型も共に、相互運用マーシャリング サービスでサポートされています。ただし、マーシャリング時に変換を必要とする型は、blittable 型も実行しません。非 blittable 型を使用する場合は、マーシャリングに伴ってオーバーヘッドが追加されるため、注意が必要です。

文字列は特に問題があります。マネージ文字列は Unicode 文字として格納されるため、結果として Unicode 文字引数を想定しているアンマネージ コードの場合は、より効率的にマーシャリングできます。できる限り ANSI 文字で構成される文字列を使用しないのが最善の方法です。

IProvideClassInfo の実装

マネージ コードに対してアンマネージ インターフェイスをマーシャリングする場合、ランタイムは特定の型のラッパーを作成します。通常、メソッド シグネチャは、インターフェイスの型を示しますが、そのインターフェイスを実装するオブジェクトの型は不明な場合があります。オブジェクトの型が不明な場合、ランタイムは型固有のラッパーより機能の低い汎用 COM オブジェクト ラッパーによりインターフェイスをラップします。

一例として、次の COM メソッド シグネチャを考察します。

interface INeedSomethng {
   HRESULT DoSomething(IBiz *pibiz);
}

メソッドは、インポートされると、次のように変換されます。

Interface INeedSomething
   Sub DoSomething(pibiz As IBiz)
End Interface
interface INeedSomething {
   void DoSomething(IBiz pibiz);
}

INeedSomething インターフェイスを実装するマネージ オブジェクトを IBiz インターフェイスに渡す場合、IBiz をマネージ コードへ初めて導入するときに、相互運用マーシャラは、そのインターフェイスを特定の型のオブジェクト ラッパーでラップしようとします。ラッパーの正しい型を識別するには、マーシャラがインターフェイスを実装するオブジェクトの型を知っている必要があります。マーシャラがオブジェクト型の判別を試行する 1 つの方法として、IProvideClassInfo インターフェイスに対する問い合わせがあります。オブジェクトが IProvideClassInfo を実装する場合、マーシャラはオブジェクトの型を判別し、型指定されたラッパーでそのインターフェイスをラップします。

モジュール呼び出しの使用

マネージ コードとアンマネージ コード間のデータのマーシャリングには、コストがかかります。境界間の遷移を減らすことで、このコストを軽減できます。通常、遷移数を最小限に抑えたインターフェイスは、境界を遷移するたびに小さなタスクを実行するインターフェイスよりも、優れたパフォーマンスを示します。

エラー HRESULT の使用上の注意

マネージ クライアントが COM オブジェクトを呼び出す場合、ランタイムは、呼び出しからの戻り時に、マーシャラがスローする COM オブジェクトにエラー HRESULT を例外として割り当てます。マネージ例外モデルは、例外ではない場合に合わせて最適化されています。例外が発生しない場合は、例外のキャッチによって発生するオーバーヘッドはほとんどありません。これに対して例外が発生する場合は、例外をキャッチするとオーバーヘッドが大きくなることがあります。

例外は頻繁に使用せず、また、通知のためにエラー HRESULT を返すことは避けるようにします。例外の発生に備えて、エラー HRESULT を確保しておいてください。エラー HRESULTS を使いすぎるとパフォーマンスに影響が出ることを念頭においてください。

外部リソースの明示的な解放

オブジェクトの中にはその有効期間中に外部リソースを使用するオブジェクトがあります。たとえば、データベース接続によってレコードセットが更新されることもあります。通常、オブジェクトはその有効期間中に外部リソースを保持しますが、明示的なリリースではリソースを即時に返すことができます。たとえば、クラスのデストラクタでファイルを閉じたり、IUnknown.Release を使用する代わりに、ファイル オブジェクトで Close メソッドを使用できます。コード内で Close メソッドと同等の操作を行うことで、ファイル オブジェクトは存在し続けたまま、外部ファイル リソースを解放できます。

アンマネージ型の再定義の回避

既存の COM インターフェイスをマネージ コードに実装する正しい方法は、Tlbimp.exe または同等の API によってインターフェイスの定義をインポートすることから始まります。生成されるメタデータは、COM インターフェイスの互換性のある定義 (同じ IID、同じ DispId など) を提供します。

マネージ コードに COM インターフェイスを手動で再定義しないようにします。この処理には時間がかかり、既存の COM インターフェイスと互換性のあるマネージ インターフェイスが生成されることはほとんどありません。代わりに、Tlbimp.exe を使用して、定義の互換性を維持してください。

正常 HRESULT の使用の回避

例外のキャッチは、マネージ アプリケーションがエラー状況を処理するための最も普通の方法です。特別な処理を行わなくても COM 型をそのまま使用できるようにするために、ランタイムは、COM メソッドがエラー HRESULT を返すたびに、自動的に例外をスローします。

COM オブジェクトが正常 HRESULT を返すと、ランタイムは retval パラメータ内にあるすべての値を返します。既定では、HRESULT は破棄されるため、マネージ クラスが正常 HRESULT の値をチェックするのはきわめて困難になります。PreserveSigAttribute 属性によって HRESULT を保存することもできますが、その処理には労力がかかります。Tlbimp.exe または同等の API で生成されたアセンブリにこの属性を手動で追加する必要があります。

できるだけ正常 HRESULT を使用しないことをお勧めします。代わりに、Out パラメータを使用して、呼び出しの状態に関する情報を返すことができます。

モジュール関数の使用の回避

タイプ ライブラリには、モジュールで定義した関数を含めることができます。通常は、これらの関数を使用して、DLL エントリ ポイントの型情報を提供します。Tlbimp.exe はこれらの関数をインポートしません。

既定のインターフェイスでのシステム オブジェクトのメンバ使用の回避

マネージ クライアントおよび COM コクラスは、ランタイムによって提供されているラッパーのヘルプと対話します。COM 型をインポートする場合、変換プロセスは、コクラスの既定のインターフェイスにあるすべてのメソッドを System.Object クラスから派生するラッパー クラスに追加します。既定のインターフェイスのメンバに名前を付けるときは、System.Object のメンバと名前の競合が発生しないように注意してください。競合が発生すると、インポートされたメソッドによって、基本クラスのメソッドがオーバーライドされます。

既定のインターフェイスのメソッドと System.Object のメソッドが同じ機能を提供している場合には、このアクションが有利に働く場合もあります。しかし、既定のインターフェイスのメソッドが意図しない目的で使用されると、このアクションが問題となる場合があります。名前付けの競合を回避するためには、既定のインターフェイスで、Object、Equals、Finalize、GetHashCode、GetType、MemberwiseClone、ToString などの名前を使用しないようにします。

参照

参照

タイプ ライブラリ インポータ (Tlbimp.exe)

その他の技術情報

相互運用のためのデザインの考慮事項