COM クライアントが .NET オブジェクトを呼び出すと、共通言語ランタイムによって、オブジェクトのマネージド オブジェクトと COM 呼び出し可能ラッパー (CCW) が作成されます。 .NET オブジェクトを直接参照できません。COM クライアントは、マネージド オブジェクトのプロキシとして CCW を使用します。
ランタイムは、サービスを要求する COM クライアントの数に関係なく、マネージド オブジェクトに対して 1 つの CCW を作成します。 次の図に示すように、複数の COM クライアントは、INew インターフェイスを公開する CCW への参照を保持できます。 CCW は、インターフェイスを実装し、ガベージ コレクションされるマネージド オブジェクトへの 1 つの参照を保持します。 COM クライアントと .NET クライアントはどちらも、同じマネージド オブジェクトに対して同時に要求を行うことができます。
COM 呼び出し可能ラッパーは、.NET ランタイム内で実行されている他のクラスからは見えません。 主な目的は、マネージド コードとアンマネージド コードの間で呼び出しをマーシャリングすることです。ただし、CCW は、ラップするマネージド オブジェクトのオブジェクト ID とオブジェクトの有効期間も管理します。
オブジェクト ID
ランタイムは、ガベージ コレクションヒープから .NET オブジェクトのメモリを割り当てます。これにより、ランタイムは必要に応じてオブジェクトをメモリ内で移動できます。 これに対し、ランタイムは、非列ヒープから CCW のメモリを割り当て、COM クライアントがラッパーを直接参照できるようにします。
オブジェクトの有効期間
ラップする .NET クライアントとは異なり、CCW は従来の COM の方法で参照カウントされます。 CCW の参照カウントが 0 に達すると、ラッパーはマネージド オブジェクトの参照を解放します。 参照が残っていないマネージド オブジェクトは、次のガベージ コレクション サイクル中に収集されます。
COM インターフェイスのシミュレート
CCW は、COM がインターフェイス ベースの対話を実施する場合と一致する方法で、パブリックで COM に表示されるすべてのインターフェイス、データ型、および戻り値を COM クライアントに公開します。 COM クライアントの場合、.NET オブジェクトでのメソッドの呼び出しは、COM オブジェクトでのメソッドの呼び出しと同じです。
このシームレスなアプローチを作成するために、CCW は IUnknown や IDispatch などの従来の COM インターフェイスを製造します。 次の図に示すように、CCW はラップする .NET オブジェクトの 1 つの参照を保持します。 COM クライアントと .NET オブジェクトの両方が、CCW のプロキシとスタブ構築を介して相互にやり取りします。
.NET ランタイムは、マネージド環境のクラスによって明示的に実装されるインターフェイスを公開するだけでなく、オブジェクトの代わりに次の表に示す COM インターフェイスの実装を提供します。 .NET クラスは、これらのインターフェイスの独自の実装を提供することで、既定の動作をオーバーライドできます。 ただし、ランタイムは常に IUnknown インターフェイスと IDispatch インターフェイスの実装を提供します。
インターフェイス | 説明 |
---|---|
IDispatch の | 型への遅延バインディングのメカニズムを提供します。 |
IErrorInfo の | エラー、そのソース、ヘルプ ファイル、ヘルプ コンテキスト、およびエラーを定義したインターフェイスの GUID (.NET クラスの場合は常に GUID_NULL ) のテキスト説明を提供します。 |
IProvideClassInfo | COM クライアントがマネージド クラスによって実装された ITypeInfo インターフェイスにアクセスできるようにします。 COM からインポートされない型の .NET Core の COR_E_NOTSUPPORTED を返します。 |
ISupportErrorInfo | COM クライアントで、マネージド オブジェクトが IErrorInfo インターフェイスをサポートしているかどうかを判断できるようにします。 その場合は、クライアントが最新の例外オブジェクトへのポインターを取得できるようにします。 すべてのマネージド型は 、IErrorInfo インターフェイスをサポートします。 |
ITypeInfo (.NET Framework のみ) | Tlbexp.exeによって生成される型情報とまったく同じクラスの型情報を提供します。 |
の IUnknown | COM クライアントが CCW の有効期間を管理し、型強制型を提供する IUnknown インターフェイスの標準実装を提供します。 |
マネージド クラスは、次の表で説明する COM インターフェイスを提供することもできます。
インターフェイス | 説明 |
---|---|
(_classname) クラス インターフェイス | ランタイムによって公開され、明示的に定義されていないインターフェイス。マネージド オブジェクトで明示的に公開されているすべてのパブリック インターフェイス、メソッド、プロパティ、フィールドを公開します。 |
IConnectionPoint と IConnectionPointContainer | デリゲート ベースのイベントをソースとするオブジェクトのインターフェイス (イベント サブスクライバーを登録するためのインターフェイス)。 |
IDispatchEx (.NET Framework のみ) | クラスが IExpando を実装する場合にランタイムによって提供されるインターフェイス。 IDispatchEx インターフェイスは IDispatch インターフェイスの拡張機能であり、IDispatch とは異なり、メンバーの列挙、追加、削除、および大文字と小文字を区別する呼び出しを有効にします。 |
IEnumVARIANT | コレクション型クラスのインターフェイス。クラスが IEnumerable を実装している場合にコレクション内のオブジェクトを列挙します。 |
クラス インターフェイスの概要
マネージド コードで明示的に定義されていないクラス インターフェイスは、.NET オブジェクトで明示的に公開されているすべてのパブリック メソッド、プロパティ、フィールド、およびイベントを公開するインターフェイスです。 このインターフェイスは、デュアル インターフェイスまたはディスパッチ専用インターフェイスにすることができます。 クラス インターフェイスは、.NET クラス自体の名前を受け取り、その前にアンダースコアを付けます。 たとえば、哺乳類クラスの場合、クラス インターフェイスは_Mammal。
派生クラスの場合、クラス インターフェイスは基底クラスのすべてのパブリック メソッド、プロパティ、およびフィールドも公開します。 派生クラスは、各基底クラスのクラス インターフェイスも公開します。 たとえば、クラス Mammal がクラスを拡張し、それ自体が System.Object を拡張する場合、.NET オブジェクトは、_Mammal、_MammalSuperclass、および_Objectという名前の 3 つのクラス インターフェイスを COM クライアントに公開します。
たとえば、次の .NET クラスを考えてみましょう。
' Applies the ClassInterfaceAttribute to set the interface to dual.
<ClassInterface(ClassInterfaceType.AutoDual)> _
' Implicitly extends System.Object.
Public Class Mammal
Sub Eat()
Sub Breathe()
Sub Sleep()
End Class
// Applies the ClassInterfaceAttribute to set the interface to dual.
[ClassInterface(ClassInterfaceType.AutoDual)]
// Implicitly extends System.Object.
public class Mammal
{
public void Eat() {}
public void Breathe() {}
public void Sleep() {}
}
COM クライアントは、 _Mammal
という名前のクラス インターフェイスへのポインターを取得できます。 .NET Framework では、 タイプ ライブラリ エクスポーター (Tlbexp.exe) ツールを使用して、 _Mammal
インターフェイス定義を含むタイプ ライブラリを生成できます。 タイプ ライブラリ エクスポーターは、.NET Core ではサポートされていません。
Mammal
クラスが 1 つ以上のインターフェイスを実装した場合、インターフェイスはコクラスの下に表示されます。
[odl, uuid(…), hidden, dual, nonextensible, oleautomation]
interface _Mammal : IDispatch
{
[id(0x00000000), propget] HRESULT ToString([out, retval] BSTR*
pRetVal);
[id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval]
VARIANT_BOOL* pRetVal);
[id(0x60020002)] HRESULT GetHashCode([out, retval] short* pRetVal);
[id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
[id(0x6002000d)] HRESULT Eat();
[id(0x6002000e)] HRESULT Breathe();
[id(0x6002000f)] HRESULT Sleep();
}
[uuid(…)]
coclass Mammal
{
[default] interface _Mammal;
}
クラス インターフェイスの生成は省略可能です。 既定では、COM 相互運用機能は、タイプ ライブラリにエクスポートするクラスごとにディスパッチ専用インターフェイスを生成します。 クラスに ClassInterfaceAttribute を適用することで、このインターフェイスの自動作成を防止または変更できます。 クラス インターフェイスを使用すると、マネージド クラスを COM に公開する作業を容易にできますが、その使用は制限されます。
注意事項
独自のクラス インターフェイスを明示的に定義する代わりに、クラス インターフェイスを使用すると、マネージド クラスの将来のバージョン管理が複雑になります。 クラス インターフェイスを使用する前に、次のガイドラインをお読みください。
クラス インターフェイスを生成するのではなく、COM クライアントが使用する明示的なインターフェイスを定義します。
COM 相互運用機能ではクラス インターフェイスが自動的に生成されるため、クラスのバージョン後の変更によって、共通言語ランタイムによって公開されるクラス インターフェイスのレイアウトが変更される可能性があります。 COM クライアントは通常、インターフェイスのレイアウトの変更を処理するために準備ができていないため、クラスのメンバー レイアウトを変更すると中断します。
このガイドラインでは、COM クライアントに公開されるインターフェイスは変更できないという概念を強化します。 インターフェイス レイアウトを誤って並べ替えて COM クライアントを中断するリスクを軽減するには、インターフェイスを明示的に定義することで、クラスに対するすべての変更をインターフェイス レイアウトから分離します。
ClassInterfaceAttribute を使用して、クラス インターフェイスの自動生成を解除し、次のコード フラグメントに示すように、クラスの明示的なインターフェイスを実装します。
<ClassInterface(ClassInterfaceType.None)>Public Class LoanApp
Implements IExplicit
Sub M() Implements IExplicit.M
…
End Class
[ClassInterface(ClassInterfaceType.None)]
public class LoanApp : IExplicit
{
int IExplicit.M() { return 0; }
}
ClassInterfaceType.None 値を指定すると、クラス メタデータがタイプ ライブラリにエクスポートされるときに、クラス インターフェイスが生成されなくなります。 前の例では、COM クライアントは、IExplicit
インターフェイスを介してのみLoanApp
クラスにアクセスできます。
ディスパッチ識別子 (DispId) のキャッシュを回避する
クラス インターフェイスの使用は、スクリプト化されたクライアント、Microsoft Visual Basic 6.0 クライアント、またはインターフェイス メンバーの DispId をキャッシュしない遅延バインディング クライアントに対して許容されるオプションです。 DispId は、遅延バインディングを有効にするインターフェイス メンバーを識別します。
クラス インターフェイスの場合、DispId の生成は、インターフェイス内のメンバーの位置に基づいています。 メンバーの順序を変更し、クラスをタイプ ライブラリにエクスポートすると、クラス インターフェイスで生成された DispId が変更されます。
クラス インターフェイスを使用するときに遅延バインドされた COM クライアントが壊れないようにするには、ClassInterfaceType.AutoDispatch 値を使用して ClassInterfaceAttribute を適用します。 この値はディスパッチのみのクラス インターフェイスを実装しますが、タイプ ライブラリのインターフェイスの説明は省略します。 インターフェイスの説明がないと、クライアントはコンパイル時に DispId をキャッシュできません。 これはクラス インターフェイスの既定のインターフェイス型ですが、属性値を明示的に適用できます。
<ClassInterface(ClassInterfaceType.AutoDispatch)> Public Class LoanApp
Implements IAnother
Sub M() Implements IAnother.M
…
End Class
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class LoanApp
{
public int M() { return 0; }
}
実行時にインターフェイス メンバーの DispId を取得するために、COM クライアントは IDispatch.GetIdsOfNames を呼び出すことができます。 インターフェイスでメソッドを呼び出すには、返された DispId を引数として IDispatch.Invoke に渡します。
クラス インターフェイスのデュアル インターフェイス オプションの使用を制限します。
デュアル インターフェイスを使用すると、COM クライアントによるインターフェイス メンバーへの早期バインディングと遅延バインディングが可能になります。 デザイン時およびテスト時に、クラス インターフェイスをデュアルに設定すると便利な場合があります。 変更されないマネージド クラス (およびその基底クラス) の場合、このオプションも許容されます。 それ以外の場合は、クラス インターフェイスをデュアルに設定しないでください。
まれに、自動的に生成されるデュアル インターフェイスが適切な場合があります。ただし、多くの場合、バージョン関連の複雑さが生まれます。 たとえば、派生クラスのクラス インターフェイスを使用する COM クライアントは、基底クラスの変更によって簡単に中断できます。 サード パーティが基底クラスを提供する場合、クラス インターフェイスのレイアウトは制御対象外になります。 さらに、ディスパッチ専用インターフェイスとは異なり、デュアル インターフェイス (ClassInterfaceType.AutoDual) は、エクスポートされたタイプ ライブラリ内のクラス インターフェイスの説明を提供します。 このような説明により、遅延バインドされたクライアントはコンパイル時に DispId をキャッシュすることが推奨されます。
すべての COM イベント通知が遅延バインディングされていることを確認します。
既定では、COM 型情報はマネージド アセンブリに直接埋め込まれるため、プライマリ相互運用機能アセンブリ (PIA) は不要です。 ただし、埋め込み型情報の制限事項の 1 つは、事前バインドされた vtable 呼び出しによる COM イベント通知の配信をサポートせず、遅延バインディングの IDispatch::Invoke
呼び出しのみをサポートするという点です。
アプリケーションで COM イベント インターフェイス メソッドの早期バインド呼び出しが必要な場合は、Visual Studio の [相互運用機能の型の埋め込み] プロパティを true
に設定するか、プロジェクト ファイルに次の要素を含めることができます。
<EmbedInteropTypes>True</EmbedInteropTypes>
こちらも参照ください
.NET