カスタム マーシャリングのソース生成

.NET 7 では、ソース生成相互運用を使用するときに型をマーシャリングする方法をカスタマイズするための新しいメカニズムが導入されています。 P/Invoke のソース ジェネレーターでは、MarshalUsingAttributeNativeMarshallingAttribute を型のカスタム マーシャリングのインジケーターとして認識します。

NativeMarshallingAttribute は、その型の既定のカスタム マーシャリングを示すために型に適用できます。 MarshalUsingAttribute をパラメーターまたは戻り値に適用して、型の特定の使用法に対するカスタム マーシャリングを示すことができます。これは、型自体に存在する可能性のある NativeMarshallingAttribute よりも優先されます。 どちらの属性も、1 つ以上の CustomMarshallerAttribute 属性でマークされているエントリ ポイント マーシャラー型である Type を想定しています。 各 CustomMarshallerAttribute は、指定した MarshalMode の指定したマネージド型をマーシャリングするために使用する必要のあるマーシャラーの実装を示します。

マーシャラーの実装

マーシャラーの実装は、ステートレスでもステートフルでもかまいません。 マーシャラー型が static クラスの場合、ステートレスと見なされます。 値の型の場合はステートフルと見なされ、そのマーシャラーの 1 つのインスタンスを使用して、特定のパラメーターまたは戻り値をマーシャリングします。 マーシャラーがステートレスかステートフルか、マーシャリングがマネージドからアンマネージド、アンマネージドからマネージド、またはその両方に対応しているかどうかに基づいて、マーシャラーの実装には、さまざまなシェイプが想定されます。 .NET SDK には、必要な図に準拠するマーシャラーの実装に役立つアナライザーとコード修正プログラムが含まれています。

MarshalMode

CustomMarshallerAttribute で指定される MarshalMode によって、マーシャラーの実装に必要なマーシャリングのサポートとシェイプが決まります。 すべてのモードでステートレス マーシャラーの実装がサポートされます。 要素マーシャリング モードでは、ステートフル マーシャラーの実装はサポートされません。

MarshalMode 想定されるサポート ステートフルである可能性がある
ManagedToUnmanagedIn マネージドからアンマネージド はい
ManagedToUnmanagedRef マネージドからアンマネージド、アンマネージドからマネージド はい
ManagedToUnmanagedOut アンマネージドからマネージド はい
UnmanagedToManagedIn アンマネージドからマネージド はい
UnmanagedToManagedRef マネージドからアンマネージド、アンマネージドからマネージド はい
UnmanagedToManagedOut マネージドからアンマネージド はい
ElementIn マネージドからアンマネージド いいえ
ElementRef マネージドからアンマネージド、アンマネージドからマネージド いいえ
ElementOut アンマネージドからマネージド いいえ

MarshalMode.Default は、マーシャラーの実装でサポートされるすべてのモードに使用する必要があることを示します。 より具体的な MarshalMode のマーシャラーの実装も指定されている場合は、MarshalMode.Default より優先されます。

基本的な使用方法

static クラスまたは struct のいずれかであるエントリ ポイント マーシャラー型を指す型で NativeMarshallingAttribute を指定できます。

[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
    public string Message;
    public int Flags;
}

エントリ ポイント マーシャラー型である ExampleMarshaller は、マーシャラー実装型を指す CustomMarshallerAttribute でマークされます。 この例では、ExampleMarshaller はエントリ ポイントと実装の両方です。 これは、値のカスタム マーシャリングで想定されるマーシャラーのシェイプに準拠しています。

[CustomMarshaller(typeof(Example), MarshalMode.Default, typeof(ExampleMarshaller))]
internal static class ExampleMarshaller
{
    public static ExampleUnmanaged ConvertToUnmanaged(Example managed)
        => throw new NotImplementedException();

    public static Example ConvertToManaged(ExampleUnmanaged unmanaged)
        => throw new NotImplementedException();

    public static void Free(ExampleUnmanaged unmanaged)
        => throw new NotImplementedException();

    internal struct ExampleUnmanaged
    {
        public IntPtr Message;
        public int Flags;
    }
}

この例の ExampleMarshaller は、マネージドからアンマネージド、およびアンマネージドからマネージドへのマーシャリングのサポートを実装するステートレス マーシャラーです。 マーシャリング ロジックは、マーシャラーの実装によって完全に制御されます。 MarshalAsAttribute を使用して構造体のフィールドがマークされますが、生成されるコードには影響しません。

その後、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 をマーシャラー エントリ ポイント型に適用して、連続するコレクション用であることを示します。 型には、関連付けられているマネージド型よりも 1 つ多く型パラメーターが必要です。 最後の型パラメーターはプレースホルダーであり、コレクションの要素型のアンマネージ型を持つソース ジェネレーターによって入力されます。

たとえば、List<T> のカスタム マーシャリングを指定できます。 次のコードでは、ListMarshaller はエントリ ポイントと実装の両方です。 これは、コレクションのカスタム マーシャリングで想定されるマーシャラーのシェイプに準拠しています。

[ContiguousCollectionMarshaller]
[CustomMarshaller(typeof(List<>), MarshalMode.Default, typeof(ListMarshaller<,>))]
public unsafe static class ListMarshaller<T, TUnmanagedElement> where TUnmanagedElement : unmanaged
{
    public static byte* AllocateContainerForUnmanagedElements(List<T> managed, out int numElements)
        => throw new NotImplementedException();

    public static ReadOnlySpan<T> GetManagedValuesSource(List<T> managed)
        => throw new NotImplementedException();

    public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(byte* unmanaged, int numElements)
        => throw new NotImplementedException();

    public static List<T> AllocateContainerForManagedElements(byte* unmanaged, int length)
        => throw new NotImplementedException();

    public static Span<T> GetManagedValuesDestination(List<T> managed)
        => throw new NotImplementedException();

    public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(byte* nativeValue, int numElements)
        => throw new NotImplementedException();

    public static void Free(byte* unmanaged)
        => throw new NotImplementedException();
}

この例の ListMarshaller は、List<T> のマネージドからアンマネージド、およびアンマネージドからマネージドへのマーシャリングのサポートを実装するステートレス コレクション マーシャラーです。 次の P/Invoke の例では、パラメーターをマネージドからアンマネージドにマーシャリングし、戻り値をアンマネージドからマネージドにマーシャリングするために ListMarshaller が使用されます。 CountElementName は、アンマネージからマネージドへの戻り値をマーシャリングするときに、numValues パラメーターを要素数として使用する必要があることを示します。

[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = "numValues")]
internal static partial void ConvertList(
    [MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
    out int numValues);

こちらもご覧ください