自定义封送的源生成
.NET 7 引入了一种新的机制,用于在使用源生成的互操作时自定义如何封送类型。 P/Invoke 的源生成器将 MarshalUsingAttribute 和 NativeMarshallingAttribute 识别为类型的自定义封送的指示器。
NativeMarshallingAttribute 可应用于类型,以指示该类型的默认自定义封送。 MarshalUsingAttribute 可应用于参数或返回值,以指示该类型的特定用法的自定义封送,优先于可能在该类型本身上的任何 NativeMarshallingAttribute。 这两个属性都需要使用一个或多个 CustomMarshallerAttribute 属性标记的 Type(入口点封送处理程序类型)。 每个 CustomMarshallerAttribute 指示应使用哪个封送处理程序实现来封送指定 MarshalMode 的指定托管类型。
封送处理程序实现
封送处理程序实现可以是无状态的,也可以是有状态的。 如果封送处理程序类型为 static
类,则被视为无状态。 如果是值类型,则被视为有状态,并且该封送处理程序的一个实例将用于封送特定参数或返回值。 根据封送处理程序是无状态的还是有状态的,以及它是否支持从托管到非托管、非托管到托管或两者同时进行封送,预计会有不同的封送处理程序实现形状。 .NET SDK 包括分析器和代码修复程序,以帮助实现符合所需形状的封送处理程序。
MarshalMode
CustomMarshallerAttribute 中指定的 MarshalMode 确定了封送处理程序实现的预期封送支持和形状。 所有模式都支持无状态封送处理程序实现。 元素封送模式不支持有状态封送处理程序实现。
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
是入口点和实现。 它符合值的自定义封送所需的封送处理程序形状。
[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 标记结构上的字段对生成的代码没有影响。
然后可以在 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)
=> 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);