次の方法で共有


COM 相互運用のために .NET 型を修飾する

.NET 型を COM に公開する

アセンブリ内の型を COM アプリケーションに公開する場合は、設計時に COM 相互運用機能の要件を検討してください。 マネージド型 (クラス、インターフェイス、構造体、および列挙型) は、次のガイドラインに従うと、COM 型とシームレスに統合されます。

  • クラスはインターフェイスを明示的に実装する必要があります。

    COM 相互運用機能は、クラスのすべてのメンバーとその基底クラスのメンバーを含むインターフェイスを自動的に生成するメカニズムを提供しますが、明示的なインターフェイスを提供する方がはるかに優れています。 自動的に生成されるインターフェイスは、クラス インターフェイスと呼ばれます。 ガイドラインについては、「 クラス インターフェイスの概要」を参照してください。

    Visual Basic、C#、および C++ を使用して、インターフェイス定義言語 (IDL) や同等のインターフェイス定義を使用する代わりに、コードにインターフェイス定義を組み込むことができます。 構文の詳細については、言語のドキュメントを参照してください。

  • マネージド型はパブリックである必要があります。

    アセンブリ内のパブリック型のみが登録され、タイプ ライブラリにエクスポートされます。 その結果、COM にはパブリック型のみが表示されます。

    マネージド型は、COM に公開されない可能性がある他のマネージド コードに機能を公開します。 たとえば、パラメーター化されたコンストラクター、静的メソッド、定数フィールドは COM クライアントに公開されません。 さらに、ランタイムがデータを型に移動したり、型から取り出したりする際に、データがコピーされたり変換されたりする可能性があります。

  • メソッド、プロパティ、フィールド、およびイベントはパブリックである必要があります。

    パブリック型のメンバーが COM に表示される場合は、パブリックである必要もあります。 ComVisibleAttributeを適用することで、アセンブリ、パブリック型、またはパブリック型のパブリック メンバーの可視性を制限できます。 既定では、すべてのパブリック型とメンバーが表示されます。

  • COM からアクティブ化するには、型にパブリック パラメーターなしのコンストラクターが必要です。

    マネージドパブリック型は COM に表示されます。 ただし、パブリック パラメーターなしのコンストラクター (引数のないコンストラクター) がない場合、COM クライアントは型を作成できません。 COM クライアントは、他の方法でアクティブ化されている場合でも、その型を使用できます。

  • 型を抽象にすることはできません。

    COM クライアントも .NET クライアントも抽象型を作成できません。

COM にエクスポートすると、マネージド型の継承階層がフラット化されます。 また、バージョン管理は、マネージド環境とアンマネージド環境で異なります。 COM に公開される型は、他のマネージド型と同じバージョン管理特性を持っていません。

.NET から COM 型を使用する

.NET から COM 型を使用する予定で、 Tlbimp.exe (タイプ ライブラリ インポーター) などのツールを使用しない場合は、次のガイドラインに従う必要があります。

  • インターフェイスには、 ComImportAttribute が適用されている必要があります。
  • インターフェイスには、COM インターフェイスのインターフェイス ID で GuidAttribute が適用されている必要があります。
  • インターフェイスには、このインターフェイスの基本インターフェイスの種類 (InterfaceTypeAttributeIUnknown、またはIDispatch) を指定するIInspectableが適用されている必要があります。
    • 既定のオプションは、 IDispatch の基本型を持ち、宣言されたメソッドをインターフェイスの想定される仮想関数テーブルに追加することです。
    • 基本型の IInspectableの指定は、.NET Framework でのみサポートされています。

これらのガイドラインは、一般的なシナリオの最小要件を提供します。 さらに多くのカスタマイズ オプションが存在し、「 相互運用属性の適用」で説明されています。

.NET で COM インターフェイスを定義する

.NET コードは、 ComImportAttribute 属性を持つインターフェイスを介して COM オブジェクトのメソッドを呼び出そうとするときに、仮想関数テーブル (vtable または vftable とも呼ばれます) を構築して、呼び出すネイティブ コードを決定するために、インターフェイスの .NET 定義を形成する必要があります。 このプロセスは複雑です。 次の例は、いくつかの単純なケースを示しています。

いくつかの方法を持つ COM インターフェイスについて考えてみましょう。

struct IComInterface : public IUnknown
{
    STDMETHOD(Method)() = 0;
    STDMETHOD(Method2)() = 0;
};

このインターフェイスでは、次の表に仮想関数テーブルのレイアウトについて説明します。

IComInterface 仮想関数テーブルのスロット メソッド名
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2

各メソッドは、宣言された順序で仮想関数テーブルに追加されます。 特定の順序は C++ コンパイラによって定義されますが、オーバーロードのない単純なケースでは、宣言順序によってテーブル内の順序が定義されます。

このインターフェイスに対応する .NET インターフェイスを次のように宣言します。

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid(/* The IID for IComInterface */)]
interface IComInterface
{
    void Method();
    void Method2();
}

InterfaceTypeAttributeは、基本インターフェイスを指定します。 いくつかのオプションが用意されています。

ComInterfaceType 基本インターフェイスの種類 属性付きインターフェイスでのメンバーの動作
InterfaceIsIUnknown IUnknown 仮想関数テーブルには最初に IUnknownのメンバーがあり、次に宣言順にこのインターフェイスのメンバーが含まれます。
InterfaceIsIDispatch IDispatch メンバーは仮想関数テーブルに追加されません。 IDispatch経由でのみアクセスできます。
InterfaceIsDual IDispatch 仮想関数テーブルには最初に IDispatchのメンバーがあり、次に宣言順にこのインターフェイスのメンバーが含まれます。
InterfaceIsIInspectable IInspectable 仮想関数テーブルには最初に IInspectableのメンバーがあり、次に宣言順にこのインターフェイスのメンバーが含まれます。 .NET Framework でのみサポートされます。

COM インターフェイスの継承と .NET

ComImportAttributeを使用する COM 相互運用システムはインターフェイスの継承と対話しないため、いくつかの軽減手順を実行しない限り、予期しない動作が発生する可能性があります。

System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute属性を使用する COM ソース ジェネレーターは、インターフェイスの継承と対話するため、期待どおりに動作します。

C++ での COM インターフェイスの継承

C++ では、開発者は、他の COM インターフェイスから派生する COM インターフェイスを次のように宣言できます。

struct IComInterface : public IUnknown
{
    STDMETHOD(Method)() = 0;
    STDMETHOD(Method2)() = 0;
};

struct IComInterface2 : public IComInterface
{
    STDMETHOD(Method3)() = 0;
};

この宣言スタイルは、既存のインターフェイスを変更せずに COM オブジェクトにメソッドを追加するメカニズムとして定期的に使用されます。これは破壊的変更になります。 この継承メカニズムにより、次の仮想関数テーブル レイアウトが作成されます。

IComInterface 仮想関数テーブルのスロット メソッド名
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 仮想関数テーブルのスロット メソッド名
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

その結果、IComInterfaceからIComInterface2*で定義されたメソッドを簡単に呼び出します。 具体的には、基本インターフェイスでメソッドを呼び出しても、基本インターフェイスへのポインターを取得するために QueryInterface の呼び出しは必要ありません。 さらに、C++ では、 IComInterface2* から IComInterface*への暗黙的な変換が可能になります。これは明確に定義されており、 QueryInterface を再度呼び出すのを回避できます。 その結果、C または C++ では、必要がない場合に基本型に到達するために QueryInterface を呼び出す必要がないため、パフォーマンスが向上する可能性があります。

WinRT インターフェイスは、この継承モデルに従っていません。 これらは、.NET の [ComImport] ベースの COM 相互運用モデルと同じモデルに従って定義されています。

ComImportAttribute を使用したインターフェース継承

.NET では、インターフェイスの継承のように見える C# コードは、実際にはインターフェイスの継承ではありません。 次のコードについて考えてみましょう。

interface I
{
    void Method1();
}
interface J : I
{
    void Method2();
}

このコードでは、「JIを実装する」とは言いません。このコードは実際には、「 J を実装するすべての型も Iを実装する必要があります」と述べています。この違いは、 ComImportAttributeベースの相互運用機能の非言語的なインターフェイス継承を行う基本的な設計上の決定につながります。 インターフェイスは常に単独で考慮されます。インターフェイスの基本インターフェイス リストは、特定の .NET インターフェイスの仮想関数テーブルを決定する計算には影響しません。

その結果、以前の C++ COM インターフェイスの例における自然な相当物は、異なる仮想関数テーブルのレイアウトをもたらします。

C# コード:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    void Method3();
}

仮想関数テーブルのレイアウト:

IComInterface 仮想関数テーブルのスロット メソッド名
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 仮想関数テーブルのスロット メソッド名
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface2::Method3

これらの仮想関数テーブルは C++ の例とは異なるため、実行時に重大な問題が発生します。 ComImportAttributeを使用した .NET でのこれらのインターフェイスの正しい定義は次のとおりです。

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    new void Method();
    new void Method2();
    void Method3();
}

メタデータ レベルでは、 IComInterface2IComInterface を実装しませんが、 IComInterface2 の実装者が IComInterfaceも実装する必要があることを指定するだけです。 したがって、基本インターフェイス型の各メソッドは再宣言する必要があります。

GeneratedComInterfaceAttributeを使用したインターフェイスの継承 (.NET 8 以降)

GeneratedComInterfaceAttributeによってトリガーされる COM ソース ジェネレーターは、C# インターフェイス継承を COM インターフェイス継承として実装するため、仮想関数テーブルは想定どおりにレイアウトされます。 前の例を使用した場合、.NET で System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute を使用したこれらのインターフェイスの正しい定義は次のようになります。

[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    void Method3();
}

基本インターフェイスのメソッドは再宣言する必要はありません。また、再宣言する必要はありません。 次の表では、結果として得られる仮想関数テーブルについて説明します。

IComInterface 仮想関数テーブルのスロット メソッド名
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 仮想関数テーブルのスロット メソッド名
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

ご覧のように、これらのテーブルは C++ の例と一致するため、これらのインターフェイスは正しく機能します。

こちらも参照ください