.NET 7 では、ソース生成相互運用機能を使用する場合に型をマーシャリングする方法をカスタマイズするための新しいメカニズムが導入されています。 P/Invokes のソース ジェネレーターは、型のカスタム マーシャリングのインジケーターとしてMarshalUsingAttributeとNativeMarshallingAttributeを認識します。
NativeMarshallingAttribute を型に適用して、その型の既定のカスタム マーシャリングを示すことができます。 MarshalUsingAttributeをパラメーターまたは戻り値に適用して、その型の特定の使用法のカスタム マーシャリングを示すことができます。これは、型自体に存在する可能性のあるNativeMarshallingAttributeよりも優先されます。 どちらの属性も、1 つ以上の CustomMarshallerAttribute 属性でマークされているエントリ ポイント マーシャラー型である Type を想定しています。 各 CustomMarshallerAttribute は、指定した MarshalModeの指定したマネージド型をマーシャリングするために使用するマーシャラー実装を示します。
マーシャラーの実装
カスタム マーシャラーの実装は、ステートレスでもステートフルでもかまいません。 マーシャラー型が static
クラスの場合、ステートレスと見なされ、実装メソッドは呼び出し間で状態を追跡しないようにする必要があります。 値型の場合はステートフルと見なされ、そのマーシャラーの 1 つのインスタンスを使用して、特定のパラメーターまたは戻り値をマーシャリングします。 一意のインスタンスを使用すると、マーシャリングプロセスとマーシャリング解除プロセス全体で状態を保持できます。
マーシャラー図形
マーシャリング ジェネレーターがカスタム マーシャラー型から期待するメソッドのセットは、 マーシャラー図形と呼ばれます。 .NET Standard 2.0 (静的インターフェイス メソッドをサポートしない) でステートレスな静的カスタム マーシャラー型をサポートし、パフォーマンスを向上させるために、インターフェイス型はマーシャラー図形の定義と実装には使用されません。 代わりに、図形は カスタム マーシャラー図形 に関する記事に記載されています。 予期されるメソッド(または形状)は、マーシャラーがステートレスかステートフルかに加えて、マネージドからアンマネージド、アンマネージドからマネージド、または両方のマーシャリングをサポートしているか(CustomMarshallerAttribute.MarshalMode
で宣言)によって異なります。 .NET SDK には、必要な図形に準拠するマーシャラーの実装に役立つアナライザーとコード修正プログラムが含まれています。
MarshalMode
CustomMarshallerAttribute で指定する MarshalMode によって、マーシャラーの実装に必要なマーシャリングのサポートと形状が決まります。 すべてのモードでステートレス マーシャラーの実装がサポートされます。 要素マーシャリング モードでは、ステートフル マーシャラーの実装はサポートされていません。
MarshalMode |
予想されるサポート | ステートフルである可能性がある |
---|---|---|
ManagedToUnmanagedIn | 管理された状態から非管理の状態へ | イエス |
ManagedToUnmanagedRef | マネージドからアンマネージド、アンマネージドからマネージド | イエス |
ManagedToUnmanagedOut | 非管理から管理へ | イエス |
UnmanagedToManagedIn | 非管理から管理へ | イエス |
UnmanagedToManagedRef | マネージドからアンマネージド、アンマネージドからマネージド | イエス |
UnmanagedToManagedOut | 管理された状態から非管理の状態へ | イエス |
ElementIn | 管理された状態から非管理の状態へ | いいえ |
ElementRef | マネージドからアンマネージド、アンマネージドからマネージド | いいえ |
ElementOut | 非管理から管理へ | いいえ |
MarshalMode.Defaultを使用して、マーシャラーの実装が実装されているメソッドに基づいて、サポートされている任意のモードに適用されることを示します。 より具体的な MarshalMode
にマーシャラーを指定した場合、そのマーシャラーは Default
としてマークされたマーシャラーよりも優先されます。
基本的な使用方法
単一の値のマーシャリング
型のカスタム マーシャラーを作成するには、必要なマーシャリング メソッドを実装するエントリ ポイント マーシャラー型を定義する必要があります。 エントリ ポイント マーシャラーの種類は、 static
クラスまたは struct
にすることができ、 CustomMarshallerAttributeでマークする必要があります。
たとえば、マネージド コードとアンマネージド コードの間でマーシャリングする単純型を考えてみます。
public struct Example
{
public string Message;
public int Flags;
}
マーシャラーの種類を定義する
CustomMarshallerAttributeでマークされたExampleMarshaller
という型を作成して、Example
型のカスタム マーシャリング情報を提供するエントリ ポイント マーシャラー型であることを示すことができます。 CustomMarshallerAttribute
の最初の引数は、マーシャラーがターゲットとするマネージド型です。 2 番目の引数は、マーシャラーがサポートする MarshalMode
です。 3 番目の引数は、マーシャラー型自体、つまり、想定される図形のメソッドを実装する型です。
[CustomMarshaller(typeof(Example), MarshalMode.Default, typeof(ExampleMarshaller))]
internal static unsafe class ExampleMarshaller
{
public static ExampleUnmanaged ConvertToUnmanaged(Example managed)
{
return new ExampleUnmanaged()
{
Message = (IntPtr)Utf8StringMarshaller.ConvertToUnmanaged(managed.Message),
Flags = managed.Flags
};
}
public static Example ConvertToManaged(ExampleUnmanaged unmanaged)
{
return new Example()
{
Message = Utf8StringMarshaller.ConvertToManaged((byte*)unmanaged.Message),
Flags = unmanaged.Flags
};
}
public static void Free(ExampleUnmanaged unmanaged)
{
Utf8StringMarshaller.Free((byte*)unmanaged.Message);
}
internal struct ExampleUnmanaged
{
public IntPtr Message;
public int Flags;
}
}
次に示す ExampleMarshaller
は、マネージド Example
型からネイティブ コードが想定する形式 (ExampleUnmanaged
) における blittable 表現へのステートレス マーシャリングを実装し、元の形式に戻します。 Free
メソッドは、マーシャリング プロセス中に割り当てられたアンマネージド リソースを解放するために使用されます。 マーシャリング ロジックは、マーシャラーの実装によって完全に制御されます。 MarshalAsAttributeを使用して構造体のフィールドをマークしても、生成されたコードには影響しません。
ここで、 ExampleMarshaller
はエントリ ポイント型と実装型の両方です。 ただし、必要に応じて、モードごとに個別のマーシャラーの種類を作成することで、さまざまなモードのマーシャリングをカスタマイズできます。 次のクラスのように、モードごとに新しい CustomMarshallerAttribute
を追加します。 通常、これはステートフル マーシャラーでのみ必要です。マーシャラー型は、呼び出し全体で状態を維持する struct
です。 慣例として、実装タイプはエントリポイントのマーシャラー タイプ内に入れ子にされています。
[CustomMarshaller(typeof(Example), MarshalMode.ManagedToUnmanagedIn, typeof(ExampleMarshaller.ManagedToUnmanagedIn))]
[CustomMarshaller(typeof(Example), MarshalMode.ManagedToUnmanagedOut, typeof(ExampleMarshaller.UnmanagedToManagedOut))]
internal static class ExampleMarshaller
{
internal struct ManagedToUnmanagedIn
{
public void FromManaged(TManaged managed) => throw new NotImplementedException();
public TNative ToUnmanaged() => throw new NotImplementedException();
public void Free() => throw new NotImplementedException()
}
internal struct UnmanagedToManagedOut
{
public void FromUnmanaged(TNative unmanaged) => throw new NotImplementedException();
public TManaged ToManaged() => throw new NotImplementedException();
public void Free() => throw new NotImplementedException();
}
}
使用するマーシャラーを宣言する
マーシャラー型を作成したら、相互運用メソッド シグネチャの MarshalUsingAttribute を使用して、特定のパラメーターまたは戻り値にこのマーシャラーを使用することを示すことができます。 MarshalUsingAttributeは、エントリ ポイント マーシャラー型を引数として受け取ります。この場合はExampleMarshaller
。
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ExampleMarshaller))]
internal static partial Example ConvertExample(
[MarshalUsing(typeof(ExampleMarshaller))] Example example);
Example
型を使用するたびにマーシャラーの種類を指定する必要がないように、NativeMarshallingAttributeをExample
型自体に適用することもできます。 これは、相互運用ソース生成で Example
型のすべての使用法に対して、指定されたマーシャラーを既定で使用する必要があることを示します。
[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
public string Message;
public int Flags;
}
Example
型は、マーシャラー型を指定せずに、ソースで生成された P/Invoke メソッドで使用できます。 次の P/Invoke の例では、 ExampleMarshaller
を使用して、パラメーターをマネージドからアンマネージドにマーシャリングします。 また、アンマネージからマネージドへの戻り値のマーシャリングにも使用されます。
[LibraryImport("nativelib")]
internal static partial Example ConvertExample(Example example);
Example
型の特定のパラメーターまたは戻り値に対して別のマーシャラーを使用するには、使用サイトでMarshalUsingAttributeを指定します。 次の P/Invoke の例では、 ExampleMarshaller
を使用して、パラメーターをマネージドからアンマネージドにマーシャリングします。 OtherExampleMarshaller
はアンマネージドからマネージドへの戻り値のマーシャリングに使用されます。
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(OtherExampleMarshaller))]
internal static partial Example ConvertExample(Example example);
マーシャリングのコレクション
非ジェネリック コレクション
要素の型に対してジェネリックではないコレクションの場合は、前に示したような単純なマーシャラー型を作成する必要があります。
ジェネリック コレクション
ジェネリック コレクション型のカスタム マーシャラーを作成するには、 ContiguousCollectionMarshallerAttribute 属性を使用できます。 この属性は、マーシャラーが配列やリストなどの連続するコレクション用であり、マーシャラーがコレクションの要素のマーシャリングをサポートするために実装する必要がある一連のメソッドを提供することを示します。 マーシャリングされるコレクションの要素型には、前に説明したメソッドを使用してマーシャラーも定義されている必要があります。
マーシャラー エントリ ポイント型に ContiguousCollectionMarshallerAttribute を適用して、連続するコレクション用であることを示します。 マーシャラー エントリ ポイント型には、関連付けられているマネージド型よりも 1 つ多くの型パラメーターが必要です。 最後の型パラメーターはプレースホルダーで、コレクションの要素型に対応するアンマネージ型がソース ジェネレーターによって入力されます。
たとえば、 List<T>のカスタム マーシャリングを指定できます。 次のコードでは、 ListMarshaller
はエントリ ポイントと実装の両方です。 これは、コレクションのカスタム マーシャリングに必要な マーシャラー図形 のいずれかに準拠しています。 (不完全な例であることに注意してください)。
[ContiguousCollectionMarshaller]
[CustomMarshaller(typeof(List<>), MarshalMode.Default, typeof(ListMarshaller<,>.DefaultMarshaller))]
public unsafe static class ListMarshaller<T, TUnmanagedElement> where TUnmanagedElement : unmanaged
{
public static class DefaultMarshaller
{
public static byte* AllocateContainerForUnmanagedElements(List<T> managed, out int numElements)
{
numElements = managed.Count;
nuint collectionSizeInBytes = managed.Count * /* size of T */;
return (byte*)NativeMemory.Alloc(collectionSizeInBytes);
}
public static ReadOnlySpan<T> GetManagedValuesSource(List<T> managed)
=> CollectionsMarshal.AsSpan(managed);
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(byte* unmanaged, int numElements)
=> new Span<TUnmanagedElement>((TUnmanagedElement*)unmanaged, numElements);
public static List<T> AllocateContainerForManagedElements(byte* unmanaged, int length)
=> new List<T>(length);
public static Span<T> GetManagedValuesDestination(List<T> managed)
=> CollectionsMarshal.AsSpan(managed);
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(byte* nativeValue, int numElements)
=> new ReadOnlySpan<TUnmanagedElement>((TUnmanagedElement*)nativeValue, numElements);
public static void Free(byte* unmanaged)
=> NativeMemory.Free(unmanaged);
}
}
この例の ListMarshaller
は、List<T> のマネージドからアンマネージド、およびアンマネージドからマネージドへのマーシャリングのサポートを実装するステートレス コレクション マーシャラーです。 次の P/Invoke の例では、 ListMarshaller
を使用して、パラメーターのコレクション コンテナーをマネージドからアンマネージドにマーシャリングし、アンマネージドからマネージドへの戻り値のコレクション コンテナーをマーシャリングします。 ソース ジェネレーターは、パラメーター list
からマーシャラーによって提供されるコンテナーに要素をコピーするコードを生成します。 int
は blittable であるため、要素自体をマーシャリングする必要はありません。 CountElementName は、アンマネージからマネージドへの戻り値のマーシャリング時に、numValues
パラメーターを要素数として使用することを示します。
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))]
internal static partial List<int> ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
out int numValues);
コレクションの要素型がカスタム型の場合は、ElementIndirectionDepth = 1
で追加のMarshalUsingAttributeを使用して、その要素マーシャラーを指定できます。
ListMarshaller
はコレクション コンテナーを処理し、ExampleMarshaller
は各要素をアンマネージドからマネージドに、またはその逆にマーシャリングします。 ElementIndirectionDepth
は、マーシャラーをコレクションの要素に適用する必要があることを示します。この要素は、コレクション自体よりも 1 レベル深くなります。
[LibraryImport("nativelib")]
[MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))]
[MarshalUsing(typeof(ExampleMarshaller), ElementIndirectionDepth = 1)]
internal static partial void ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))]
[MarshalUsing(typeof(ExampleMarshaller), ElementIndirectionDepth = 1)]
List<Example> list,
out int numValues);
こちらも参照ください
.NET