自訂封送處理的來源產生
.NET 7 導入新的機制,可讓您自訂在使用來源產生的 Interop 時如何封送處理型別。 P/Invokes 的來源產生器會辨識 MarshalUsingAttribute 和 NativeMarshallingAttribute 作為型別的自訂封送處理的指標。
NativeMarshallingAttribute 可套用至型別以表示該型別的預設自訂封送處理。 MarshalUsingAttribute 可套用至參數或傳回值以表示型別特定使用方式的自訂封送處理,優先順序高於型別本身上的任何 NativeMarshallingAttribute。 這兩個屬性都需要已一或多個 CustomMarshallerAttribute 屬性標示的進入點封送處理器型別 Type。 每個 CustomMarshallerAttribute 都會指出應使用哪一個封送處理器實作來封送處理指定 MarshalMode 的受控型別。
封送處理器實作
封送處理器實作可為無狀態或具狀態。 如果封送處理型別為 static
類別,則會被視為無型別。 如果是實值型別,則會被視為具狀態且該封送處理器的一個執行個體將用來封送處理特定參數或傳回值。 不同的封裝處理器實作圖形取決於封裝處理器是否為無狀態或具狀態,以及是否支援從受控到非受控、從非受控到受控 (或兩者) 的封裝處理。 .NET SDK 包含分析器和程式碼修正程式,可協助實作符合所需圖形的封送處理器。
MarshalMode
CustomMarshallerAttribute 中指定的 MarshalMode 會決定封送處理器的預期封送處理支援和圖形。 所有模式都支援無狀態封裝處理器實作。 元素封裝處理模式不支援具狀態封裝處理器實作。
MarshalMode |
預期的支援 | 可為具狀態 |
---|---|---|
ManagedToUnmanagedIn | 受控到非受控 | Yes |
ManagedToUnmanagedRef | 受控到非受控和非受控到受控 | Yes |
ManagedToUnmanagedOut | 非受控到受控 | Yes |
UnmanagedToManagedIn | 非受控到受控 | Yes |
UnmanagedToManagedRef | 受控到非受控和非受控到受控 | Yes |
UnmanagedToManagedOut | 受控到非受控 | Yes |
ElementIn | 受控到非受控 | No |
ElementRef | 受控到非受控和非受控到受控 | No |
ElementOut | 非受控到受控 | No |
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 標記結構上的欄位不會影響產生的程式碼。
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 套用至封裝處理器進入點型別以表示用於連續集合。 相較於相關聯的受控型別,型別必須有一或多個型別參數。 最後一個型別參數為預留位置,而且會由來源產生器瑱入集合元素型別的非受控行別。
例如,您可指定 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);