ComWrappers のソース生成
.NET 8 には、ComWrappers API の実装を作成するソース ジェネレーターが導入されています。 ジェネレーターは GeneratedComInterfaceAttribute を認識します。
.NET ランタイムの組み込みの (ソース生成ではない) Winodws 専用 COM 相互運用システムは、実行時に IL スタブ (JIT 化された IL 命令のストリーム) を生成して、マネージド コードから COM へと、その逆の移行をしやすくします。 この IL スタブは実行時に生成されるため、NativeAOT および IL トリミングと互換性がありません。 実行時のスタブ生成によって、マーシャリングの問題の診断が困難になる場合もあります。
組み込みの相互運用機能は、実行時のコード生成に依存する ComImport
や DllImport
などの属性を使用します。 この例を次のコードに示します。
[ComImport]
interface IFoo
{
void Method(int i);
}
[DllImport("MyComObjectProvider")]
static nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();
[DllImport("MyComObjectProvider")]
static void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);
// Use the system to create a Runtime Callable Wrapper to use in managed code
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)Marshal.GetObjectForIUnknown(ptr);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
IFoo foo = GetManagedIFoo();
nint ptr = Marshal.GetIUnknownForObject(foo);
GivePointerToComInterface(ptr);
ComWrappers
API を使用すると、組み込みの COM システムを使用せずに C# で COM と対話できますが、多くの定型コードと手動で記述されたアンセーフ コードを必要とします。 COM インターフェイス ジェネレーターは、このプロセスを自動化し、ComWrappers
を組み込みの COM と同じくらい簡単に作成しますが、トリミング可能かつ AOT を利用しやすい方法で提供します。
基本的な使用
COM インターフェイス ジェネレーターを使用するには、COM からインポートするか、それに対して公開するインターフェイス定義に GeneratedComInterfaceAttribute と GuidAttribute 属性を追加します。 型には partial
のマークを付け、生成されたコードにアクセスできるように可視性を internal
または public
にする必要があります。
[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
internal partial interface IFoo
{
void Method(int i);
}
次に、インターフェイスを実装するクラスを COM に公開するために、実装するクラスに GeneratedComClassAttribute を追加します。 このクラスも partial
、かつ internal
と public
のいずれかである必要があります。
[GeneratedComClass]
internal partial class Foo : IFoo
{
public void Method(int i)
{
// Do things
}
}
コンパイル時に、ジェネレーターが ComWrappers API の実装を作成します。これで、StrategyBasedComWrappers 型またはカスタム派生型を使用して COM インターフェイスを使用または公開できます。
[LibraryImport("MyComObjectProvider")]
private static partial nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();
[LibraryImport("MyComObjectProvider")]
private static partial void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);
// Use the ComWrappers API to create a Runtime Callable Wrapper to use in managed code
ComWrappers cw = new StrategyBasedComWrappers();
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)cw.GetOrCreateObjectForComInstance(ptr, CreateObjectFlags.None);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
ComWrappers cw = new StrategyBasedComWrappers();
Foo foo = new();
nint ptr = cw.GetOrCreateComInterfaceForObject(foo, CreateComInterfaceFlags.None);
GivePointerToComInterface(ptr);
マーシャリングをカスタマイズする
COM インターフェイス ジェネレーターは、MarshalUsingAttribute 属性と MarshalAsAttribute 属性の一部の使用法を考慮して、パラメーターのマーシャリングをカスタマイズします。 詳細については、MarshalUsing
属性を使用したソース生成マーシャリングのカスタマイズに関する記事と、MarshalAs
属性を使用したパラメーターのマーシャリングのカスタマイズに関する記事を参照してください。 他のマーシャリング属性がない場合は、インターフェイス内の型 string
のすべてのパラメーターと戻り値に GeneratedComInterfaceAttribute.StringMarshalling と GeneratedComInterfaceAttribute.StringMarshallingCustomType プロパティが適用されます。
暗黙的な HRESULT と PreserveSig
C# の COM メソッドのシグネチャは、ネイティブ メソッドとは異なります。 標準の COM の戻り値の型は、エラーと成功の状態を表す 4 バイトの整数型である HRESULT
です。 この HRESULT
戻り値は、既定では C# シグネチャ内で非表示で、エラー値が返されるときに例外に変換されます。 ネイティブの COM のシグネチャの最後の "out" パラメーターを、必要に応じて C# シグネチャの戻り値に変換できます。
たとえば、次のスニペットは、C# メソッドのシグネチャと、ジェネレーターが推論する対応するネイティブのシグネチャを示しています。
void Method1(int i);
int Method2(float i);
HRESULT Method1(int i);
HRESULT Method2(float i, _Out_ int* returnValue);
自分で HRESULT
を処理する場合は、メソッドの PreserveSigAttribute を使用して、ジェネレーターがこの変換を実行しないように指定できます。 次のスニペットは、[PreserveSig]
を適用すると、ジェネレーターがどのネイティブ シグネチャを想定するかを示しています。 COM メソッドは HRESULT
を返す必要があるため、PreserveSig
を指定したいずれのメソッドの戻り値も int
になります。
[PreserveSig]
int Method1(int i, out int j);
[PreserveSig]
int Method2(float i);
HRESULT Method1(int i, int* j);
HRESULT Method2(float i);
詳細については、「.NET の相互運用における暗黙的なメソッド シグネチャの変換」を参照してください
組み込みの COM との非互換性と相違点
IUnknown
のみ
サポートされている唯一のインターフェイス ベースは IUnknown
です。 InterfaceIsIUnknown 以外の値を持つ InterfaceTypeAttribute が指定されたインターフェイスは、ソース生成 COM ではサポートされません。 InterfaceTypeAttribute
がないインターフェイスは、IUnknown
値から派生するものとみなされます。 これは、既定値が InterfaceIsDual である組み込みの COM とは異なります。
マーシャリングの既定値とサポート
ソース生成 COM の既定のマーシャリング動作は、組み込みの COM とは一部異なります。
組み込みの COM システムでは、暗黙的な
[In, Out]
属性を持つ blittable 要素の配列を除き、すべての型に暗黙的な[In]
属性があります。 ソース生成 COM では、blittable 要素の配列を含むすべての型に[In]
セマンティクスがあります。[In]
および[Out]
属性は配列でのみ使用できます。 他の型で[Out]
または[In, Out]
動作が必要な場合は、in
およびout
パラメーター修飾子を使用します。
派生インターフェイス
組み込みの COM システムで、他の COM インターフェイスから派生するインターフェイスがある場合は、new
キーワードを使用して、基底インターフェイスの各基本メソッドに対するシャドウ メソッドを宣言する必要があります。 詳細については、「COM インターフェイスの継承と .NET」を参照してください。
[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
void Method1(int i);
void Method2(float i);
}
[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
new void Method1(int i);
new void Method2(float f);
void Method3(long l);
void Method4(double d);
}
COM インターフェイス ジェネレーターは、基本メソッドのシャドウを想定しません。 別のインターフェイスから継承するメソッドを作成するには、基底インターフェイスを C# 基底インターフェイスとして指定し、派生インターフェイスのメソッドを追加するだけです。 詳細については、デザイン ドキュメントを参照してください。
[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
void Method1(int i);
void Method2(float i);
}
[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
void Method3(long l);
void Method4(double d);
}
GeneratedComInterface
属性を持つインターフェイスは、GeneratedComInterface
属性を持つその 1 つの基底インターフェイスからのみ継承できることに注意してください。
アセンブリ境界を越えた派生インターフェイス
.NET 8 では、別のアセンブリで定義されている GeneratedComInterface
属性付きインターフェイスから派生する GeneratedComInterfaceAttribute 属性を持つインターフェイスを定義することはサポートされていません。
.NET 9 以降のバージョンでは、このシナリオは次の制限付きでサポートされています。
- 基本インターフェイス型は、派生型と同じターゲット フレームワークを対象としてコンパイルする必要があります。
- 基本インターフェイス型は、基本インターフェイスのメンバーをシャドウしないようにする必要があります (存在する場合)。
さらに、別のアセンブリで定義されている基本インターフェイス チェーンで生成された仮想メソッド オフセットに対する変更は、プロジェクトが再構築されるまで派生インターフェイスでは考慮されません。
Note
.NET 9 以降のバージョンでは、生成された COM インターフェイスをアセンブリ境界を越えて継承すると警告が生成され、この機能の使用に関する制限と落とし穴について通知されます。 この警告を無効にすると、制限事項を確認した上で、アセンブリ境界を越えて継承できます。
マーシャリング API
Marshal の一部の API は、ソース生成 COM と互換性がありません。 これらのメソッドは、ComWrappers
実装上の対応するメソッドに置き換えてください。
関連項目
.NET