.NET 7 引入了一種新的機制,用於使用來源生成的互操作性時,如何自定義類型的封送處理。 P/Invokes 的來源產生器會將 MarshalUsingAttribute 和 NativeMarshallingAttribute 辨識為自定義型別的封送處理標識。
NativeMarshallingAttribute 可以套用至類型,以指出該類型的預設自定義封送處理。 MarshalUsingAttribute可以套用至參數或傳回值,以指出該類型特定使用方式的自定義封送處理,其優先順序高於類型本身上的任何NativeMarshallingAttribute。 這兩個屬性都需要一個以一或多個 Type 屬性標示的 CustomMarshallerAttribute 進入點封送器類型。 每個 CustomMarshallerAttribute 表示應該使用哪一個封送處理器實作來封送處理指定的 MarshalModeManaged型別。
Marshaller 實作
自訂封送器的實作可以是無狀態的或有狀態的。 如果封送處理程式類型是 static 類別,則會被視為無狀態,而且實作方法不應該跨呼叫追蹤狀態。 如果它是值類型,則會被視為有狀態,而且該封送處理程式的一個實例將會用來封送處理特定參數或傳回值。 使用唯一的實例可讓您在封送處理和取消封送處理的過程中保留狀態。
馬歇爾器圖形
封送處理產生器預期自訂封送處理程式類型的一組方法稱為 封送處理程式圖形。 為了在 .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。
例如,假設您要在 Managed 和非 Managed 程式碼之間封送處理的簡單類型:
public struct Example
{
public string Message;
public int Flags;
}
定義封送處理程式類型
您可以建立一個名為 ExampleMarshaller 的類型,並使用 CustomMarshallerAttribute 標示,以指出它是進入點封送處理程式類型,提供 Example 類型的自訂封送處理資訊。
CustomMarshallerAttribute 的第一個引數是封送處理程序所針對的受控類型。 第二個引數是封送器支持的 MarshalMode。 第三個引數是封送處理程式類型本身,也就是以預期形狀實作方法的類型。
[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 類型轉換為原生程式碼預期的 blittable 表示格式 (ExampleUnmanaged),再轉換回來。 此 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 Interop 方法簽章上使用 ,以指出您想要將此封送器用於特定參數或傳回值。
MarshalUsingAttribute採用進入點封送處理器類型作為引數,此案例中為ExampleMarshaller。
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ExampleMarshaller))]
internal static partial Example ConvertExample(
[MarshalUsing(typeof(ExampleMarshaller))] Example example);
為了避免必須在每次使用 Example 類型時指定封送處理程式類型,您也可以將 NativeMarshallingAttribute 直接套用到 Example 類型本身。 這表示預設應該針對互通性來源產生中類型 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應用於封送器進入點類型,以表示它適用於連續集合。 封送器入口類型必須比其關聯的管理類型多一個類型參數。 最後一個類型參數是佔位元,來源生成器會將集合元素類型的非受控類型填入其中。
例如,您可以為 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 元素本身不需要封組。
CountElementName 表示在將傳回值從 Unmanaged 封送至 Managed 時,應使用 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);
當集合的元素類型是自訂類型時,您可以為該元素指定封送處理程序,方法是使用附加的 MarshalUsingAttribute 搭配 ElementIndirectionDepth = 1。
ListMarshaller將負責管理集合容器,並且ExampleMarshaller會將每個元素從未受控轉換為受控,再轉換回來。 表示 ElementIndirectionDepth 封送處理程式應該套用至集合的元素,這些元素比集合本身深一層。
[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);