次の方法で共有


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

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

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型を使用するたびにマーシャラーの種類を指定する必要がないように、NativeMarshallingAttributeExample型自体に適用することもできます。 これは、相互運用ソース生成で 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);

こちらも参照ください