Generazione dell'origine per il marshalling personalizzato
In .NET 7 viene introdotto un nuovo meccanismo per la personalizzazione del marshalling di un tipo quando si usa l'interoperabilità generata dall'origine. Il generatore di origini per operazioni PInvoke riconosce MarshalUsingAttribute e NativeMarshallingAttribute come indicatori per il marshalling personalizzato di un tipo.
È possibile applicare l'attributo NativeMarshallingAttribute a un tipo per indicare il marshalling personalizzato predefinito per tale tipo. È possibile applicare l'attributo MarshalUsingAttribute a un parametro o a un valore restituito per indicare il marshalling personalizzato per tale particolare utilizzo del tipo, che ha la precedenza su qualsiasi attributo NativeMarshallingAttribute impostato per il tipo stesso. Entrambi questi attributi prevedono un elemento Type, ovvero il tipo del marshaller del punto di ingresso, che è contrassegnato con uno o più attributi CustomMarshallerAttribute. Ogni attributo CustomMarshallerAttribute indica quale implementazione del marshaller deve essere usata per effettuare il marshalling del tipo gestito specificato per l'enumerazione MarshalMode specificata.
Implementazione del marshaller
Le implementazioni del marshaller possono essere senza stato o con stato. Se il tipo di marshaller è una classe static
, viene considerato senza stato. Se è un tipo valore, viene considerato con stato e verrà usata una sola un'istanza del marshaller per effettuare il marshalling di un parametro o di un valore restituito specifico. Sono previste forme diverse per l'implementazione del marshaller a seconda che sia un marshaller senza stato o con stato e che supporti il marshalling da codice gestito a codice non gestito, da codice non gestito a gestito o entrambi. .NET SDK include analizzatori e correzioni del codice per facilitare l'implementazione di marshaller conformi alle forme richieste.
MarshalMode
L'enumerazione MarshalMode specificata in un attributo CustomMarshallerAttribute determina il supporto e la forma del marshalling previsti per l'implementazione del marshaller. Tutte le modalità supportano implementazioni del marshaller senza stato. Le modalità di marshalling degli elementi non supportano implementazioni del marshaller con stato.
MarshalMode |
Supporto previsto | Può essere con stato |
---|---|---|
ManagedToUnmanagedIn | Da codice gestito a codice non gestito | Sì |
ManagedToUnmanagedRef | Da codice gestito a codice non gestito e da codice non gestito a codice gestito | Sì |
ManagedToUnmanagedOut | Da codice non gestito a codice gestito | Sì |
UnmanagedToManagedIn | Da codice non gestito a codice gestito | Sì |
UnmanagedToManagedRef | Da codice gestito a codice non gestito e da codice non gestito a codice gestito | Sì |
UnmanagedToManagedOut | Da codice gestito a codice non gestito | Sì |
ElementIn | Da codice gestito a codice non gestito | No |
ElementRef | Da codice gestito a codice non gestito e da codice non gestito a codice gestito | No |
ElementOut | Da codice non gestito a codice gestito | No |
MarshalMode.Default indica che l'implementazione del marshaller deve essere usata per qualsiasi modalità supportata. Se viene specificata anche un'implementazione del marshaller per un'enumerazione MarshalMode
più specifica, questa ha la precedenza su MarshalMode.Default
.
Utilizzo di base
È possibile specificare NativeMarshallingAttribute in un tipo, in modo che punti a un tipo del marshaller del punto di ingresso che è una classe static
o uno struct
.
[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
public string Message;
public int Flags;
}
ExampleMarshaller
, il tipo del marshaller del punto di ingresso, è contrassegnato con CustomMarshallerAttribute, in modo che punti a un tipo di implementazione del marshaller. In questo esempio ExampleMarshaller
è sia il punto di ingresso che l'implementazione. È conforme alle forme del marshaller previste per il marshalling personalizzato di un valore.
[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;
}
}
Nell'esempio ExampleMarshaller
è un marshaller senza stato che implementa il supporto per il marshalling da codice gestito a codice non gestito e da codice non gestito a codice gestito. La logica di marshalling è interamente controllata dall'implementazione del marshaller. Contrassegnare i campi in uno struct con MarshalAsAttribute non influisce in alcun modo sul codice generato.
È quindi possibile usare il tipo Example
nella generazione dell'origine per PInvoke. Nell'esempio di operazione PInvoke seguente ExampleMarshaller
verrà usato per effettuare il marshalling del parametro da codice gestito a codice non gestito. Verrà usato anche per effettuare il marshalling del valore restituito da codice non gestito a codice gestito.
[LibraryImport("nativelib")]
internal static partial Example ConvertExample(Example example);
Per usare un marshaller diverso per un utilizzo specifico del tipo Example
, specificare MarshalUsingAttribute nel sito di utilizzo. Nell'esempio di operazione PInvoke seguente ExampleMarshaller
verrà usato per effettuare il marshalling del parametro da codice gestito a codice non gestito. OtherExampleMarshaller
verrà usato per effettuare il marshalling del valore restituito da codice non gestito a codice gestito.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(OtherExampleMarshaller))]
internal static partial Example ConvertExample(Example example);
Raccolte
Applicare ContiguousCollectionMarshallerAttribute a un tipo di marshaller del punto di ingresso per indicare che viene usato per raccolte contigue. Per il tipo deve esistere un parametro di tipo in più rispetto al tipo gestito associato. L'ultimo parametro di tipo è un segnaposto e verrà inserito dal generatore di origini con il tipo non gestito per il tipo di elemento della raccolta.
Ad esempio, è possibile specificare il marshalling personalizzato per una classe List<T>. Nel codice seguente ListMarshaller
è sia il punto di ingresso che l'implementazione. È conforme alle forme del marshaller previste per il marshalling personalizzato di una raccolta.
[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();
}
Nell'esempio ListMarshaller
è un marshaller di raccolta senza stato che implementa il supporto per il marshalling da codice gestito a codice non gestito e da codice non gestito a codice gestito per una classe List<T>. Nell'esempio di operazione PInvoke seguente ListMarshaller
verrà usato per effettuare il marshalling del parametro da codice gestito a codice non gestito e per effettuare il marshalling del valore restituito da codice non gestito a codice gestito. CountElementName indica che il parametro numValues
deve essere usato come conteggio degli elementi durante il marshalling del valore restituito da codice non gestito a codice gestito.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = "numValues")]
internal static partial List<int> ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
out int numValues);