次の方法で共有


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

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

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

マーシャラーの実装

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

MarshalMode

MarshalModeで指定されたCustomMarshallerAttributeは、マーシャラー実装で想定されるマーシャリングのサポートと形状を決定します。 すべてのモードでステートレス マーシャラーの実装がサポートされます。 要素マーシャリング モードでは、ステートフル マーシャラーの実装はサポートされていません。

MarshalMode 予想されるサポート ステートフルである可能性がある
ManagedToUnmanagedIn 管理された状態から非管理の状態へ イエス
ManagedToUnmanagedRef マネージドからアンマネージド、アンマネージドからマネージドへ イエス
ManagedToUnmanagedOut 非管理から管理へ イエス
UnmanagedToManagedIn 非管理から管理へ イエス
UnmanagedToManagedRef マネージドからアンマネージド、アンマネージドからマネージドへ イエス
UnmanagedToManagedOut 管理された状態から非管理の状態へ イエス
ElementIn 管理された状態から非管理の状態へ いいえ
ElementRef マネージドからアンマネージド、アンマネージドからマネージドへ いいえ
ElementOut 非管理から管理へ いいえ

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

基本的な使用方法

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

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

ExampleMarshallerはエントリーポイントのマーシャラー型であり、CustomMarshallerAttribute型を指すによってマークされています。 この例では、 ExampleMarshaller はエントリ ポイントと実装の両方です。 これは、値のカスタム マーシャリングに必要な マーシャラー図形 に準拠しています。 ExampleMarshallerは UTF-8 文字列エンコードを前提とします。

[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 は、マネージドからアンマネージド、アンマネージドからマネージドへのマーシャリングのサポートを実装するステートレス マーシャラーです。 マーシャリング ロジックは、マーシャラーの実装によって完全に制御されます。 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)
    {
        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 を使用して、パラメーターをマネージドからアンマネージドに、戻り値をアンマネージドからマネージドにそれぞれマーシャリングします。 CountElementName は、アンマネージからマネージドへの戻り値のマーシャリング時に、numValues パラメーターを要素数として使用することを示します。

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

こちらも参照ください