自定义封送的源生成

.NET 7 引入了一种新机制,用于在使用源生成的互操作性时自定义类型的封送处理方式。 P/Invokes 的源生成器MarshalUsingAttributeNativeMarshallingAttribute识别为类型自定义封送的指示器。

NativeMarshallingAttribute 可以应用于一种类型,来指示该类型的默认自定义封送处理。 MarshalUsingAttribute可以应用于参数或返回值,以表示该类型特定用法的自定义封送,优先于可能在类型本身上的任何NativeMarshallingAttribute。 这两个属性都需要一个入口点封送器类型,并用一个或多个 CustomMarshallerAttribute 属性标记。 每个 CustomMarshallerAttribute 封送器实现都指示应使用哪个封送器实现来封送指定的 MarshalMode托管类型。

封送器实现

封送器实现可以是无状态实现,也可以是有状态的。 如果封送器类型为类 static ,则被视为无状态。 如果它是值类型,则被视为有状态,并且该封送器的一个实例将用于封送特定参数或返回值。 封送器实现的不同形式是根据封送器是无状态还是有状态,以及它是否支持从托管到非托管、非托管到托管还是两者之间的封送而有不同的变化。 .NET SDK 包括分析器和代码修复程序,可帮助实现符合所需形状的封送器。

MarshalMode

MarshalMode 指定的内容决定了 CustomMarshallerAttribute 的封送器实现的预期封送支持和 形状。 所有模式都支持无状态封送器实现。 元素封送模式不支持有状态的封送器实现。

MarshalMode 预期支持 可以是有状态的
ManagedToUnmanagedIn 托管到非托管 是的
ManagedToUnmanagedRef 托管到非托管和非托管到托管 是的
ManagedToUnmanagedOut 从非托管到托管 是的
UnmanagedToManagedIn 非托管到托管 是的
UnmanagedToManagedRef 托管到非托管和非托管到托管 是的
UnmanagedToManagedOut 托管到非托管 是的
ElementIn 托管到非托管
ElementRef 托管到非托管和非托管到托管
ElementOut 从非托管到托管

MarshalMode.Default 指示封送器实现应用于它支持的任何模式。 如果也指定了一个更具体的 MarshalMode 封送器实现,则该封送器优先于 MarshalMode.Default

基本用法

我们可以在类型上指定NativeMarshallingAttribute,该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标记结构体中的字段对生成的代码没有影响。

然后,可以在 P/Invoke 源生成中使用该 Example 类型。 在接下来的 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应用于封送器入口点类型,用于表示适用于连续集合。 该类型必须具有比关联的托管类型更多的类型参数。 最后一个类型参数是占位符,源生成器将使用集合元素类型的非托管类型来填充它。

例如,您可以为 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);

另请参阅