.NET 7 引入了一种新机制,用于在使用源生成的互操作性时自定义类型的封送处理方式。 P/Invokes 的源生成器将MarshalUsingAttribute和NativeMarshallingAttribute识别为类型自定义封送的指示器。
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);