Generación de código fuente de serialización personalizada
.NET 7 incorpora un nuevo mecanismo para personalizar cómo se serializa un tipo al usar la interoperabilidad generada mediante código fuente. El generador de código fuente de P/Invokes reconoce MarshalUsingAttribute y NativeMarshallingAttribute como indicadores de serialización personalizada de un tipo.
NativeMarshallingAttribute se puede aplicar a un tipo para indicar la serialización personalizada predeterminada de ese tipo. MarshalUsingAttribute se puede aplicar a un parámetro o un valor devuelto para indicar la serialización personalizada de ese uso concreto del tipo, teniendo prioridad sobre cualquier NativeMarshallingAttribute que pueda haber en el propio tipo. Ambos atributos esperan un Type (el tipo del serializador de punto de entrada) marcado con uno o varios atributos CustomMarshallerAttribute. Cada CustomMarshallerAttribute indica qué implementación del serializador se debe usar para serializar el tipo administrado especificado para el MarshalMode especificado.
Implementación de serializador
Las implementaciones de serializador pueden ser sin estado o con estado. Si el tipo de serializador es una clase static
, se considera sin estado. Si es un tipo de valor, se considera con estado y se usará una instancia de ese serializador para serializar un valor devuelto o un parámetro específico. Se esperan diferentes formas de implementación del serializador, dependiendo de si un serializador no tiene estado o tiene estado y de si admite la serialización de administrado a no administrado, de no administrado a administrado, o ambas. El SDK de .NET incluye analizadores y solucionadores de código que ayudan a implementar serializadores que cumplan las formas que sean necesarias.
MarshalMode
El objeto MarshalMode especificado en CustomMarshallerAttribute determina la compatibilidad de serialización esperada y la forma de implementación del serializador. Todos los modos admiten implementaciones de serializador sin estado. Los modos de serialización de elementos no admiten implementaciones de serializador con estado.
MarshalMode |
Compatibilidad esperada | Puede ser con estado |
---|---|---|
ManagedToUnmanagedIn | De administrado a no administrado | Sí |
ManagedToUnmanagedRef | De administrado a no administrado y de no administrado a administrado | Sí |
ManagedToUnmanagedOut | De no administrado a administrado | Sí |
UnmanagedToManagedIn | De no administrado a administrado | Sí |
UnmanagedToManagedRef | De administrado a no administrado y de no administrado a administrado | Sí |
UnmanagedToManagedOut | De administrado a no administrado | Sí |
ElementIn | De administrado a no administrado | No |
ElementRef | De administrado a no administrado y de no administrado a administrado | No |
ElementOut | De no administrado a administrado | No |
MarshalMode.Default indica que la implementación del serializador debe usarse con cualquier modo que sea compatible. Si se especifica también una implementación de serializador de un objeto MarshalMode
más específico, esta tiene prioridad sobre MarshalMode.Default
.
Uso básico
Podemos especificar NativeMarshallingAttribute en un tipo, apuntando a un tipo de serializador de punto de entrada que es una clase static
o struct
.
[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
public string Message;
public int Flags;
}
ExampleMarshaller
, el tipo de serializador de punto de entrada, se marca con CustomMarshallerAttribute, que apunta a un tipo de implementación de serializador. En este ejemplo, ExampleMarshaller
es a la vez el punto de entrada y la implementación. Se ajusta a las formas de serialización esperadas de serialización personalizada de un valor.
[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;
}
}
En el ejemplo, ExampleMarshaller
es un serializador sin estado que implementa compatibilidad con la serialización de administrado a no administrado y de no administrado a administrado. La lógica de serialización se controla completamente mediante la implementación del serializador. Marcar campos en una estructura sin MarshalAsAttribute no tiene ningún efecto en el código generado.
Acto seguido, el tipo Example
se puede usar en la generación de código fuente de P/Invoke. En el siguiente ejemplo de P/Invoke, ExampleMarshaller
se usará para serializar el parámetro de administrado a no administrado. También se usará para serializar el valor devuelto de no administrado a administrado.
[LibraryImport("nativelib")]
internal static partial Example ConvertExample(Example example);
Para usar otro serializador para un uso específico del tipo Example
, especifique MarshalUsingAttribute en el sitio de uso. En el siguiente ejemplo de P/Invoke, ExampleMarshaller
se usará para serializar el parámetro de administrado a no administrado. OtherExampleMarshaller
se usará para serializar el valor devuelto de no administrado a administrado.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(OtherExampleMarshaller))]
internal static partial Example ConvertExample(Example example);
Colecciones
Use ContiguousCollectionMarshallerAttribute en un tipo de punto de entrada del serializador para indicar que es para colecciones contiguas. El tipo debe tener un parámetro de tipo más que el tipo administrado asociado. El último parámetro de tipo es un marcador de posición que el generador de código fuente rellenará con el tipo no administrado del tipo de elemento de la colección.
Por ejemplo, puede especificar una serialización personalizada para List<T>. En el siguiente código, ListMarshaller
es a la vez el punto de entrada y la implementación. Se ajusta a las formas de serialización esperadas de serialización personalizada de una colección.
[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();
}
En el ejemplo, ListMarshaller
es un serializador de colección sin estado que implementa compatibilidad con la serialización de administrado a no administrado y de no administrado a administrado de un List<T>. En el siguiente ejemplo de P/Invoke, ListMarshaller
se usará para serializar el parámetro de administrado a no administrado y para serializar el valor devuelto de no administrado a administrado. CountElementName indica que el parámetro numValues
debe usarse como el número de elementos al serializar el valor devuelto de no administrado a administrado.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = "numValues")]
internal static partial void ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
out int numValues);