Compartir a través de


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
ManagedToUnmanagedRef De administrado a no administrado y de no administrado a administrado
ManagedToUnmanagedOut De no administrado a administrado
UnmanagedToManagedIn De no administrado a administrado
UnmanagedToManagedRef De administrado a no administrado y de no administrado a administrado
UnmanagedToManagedOut De administrado a no administrado
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);

Vea también