Nota
O acceso a esta páxina require autorización. Pode tentar iniciar sesión ou modificar os directorios.
O acceso a esta páxina require autorización. Pode tentar modificar os directorios.
.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 Marshaller
Las implementaciones de serializador personalizadas pueden ser sin estado o con estado. Si el tipo marshaller es una static clase, se considera sin estado y los métodos de implementación no deben realizar un seguimiento del estado entre las llamadas. 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. El uso de una instancia única permite conservar el estado durante el proceso de serialización y deserialización.
Formas de serializador
El conjunto de métodos que el generador de serialización espera de un tipo de serializador personalizado se conoce como forma de serializador. Para admitir tipos de marshallers personalizados estáticos y sin estado en .NET Standard 2.0 (que no admite métodos estáticos en interfaces) y mejorar el rendimiento, no se utilizan tipos de interfaz para definir e implementar las formas de los marshallers. En su lugar, las formas se documentan en el artículo Formas de serializador personalizado. Los métodos esperados (o la forma) dependen de si el serializador es sin estado o con estado, y si admite el paso de datos de administrados a no administrados, de no administrados a administrados, o ambos (declarados con CustomMarshallerAttribute.MarshalMode). 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 |
Soporte esperado | Puede ser estatal |
|---|---|---|
| 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 |
Use MarshalMode.Default para indicar que la implementación del marshaller se aplica a cualquier modo admitido, en función de los métodos que implementa. Si especifica un serializador para un MarshalMode más específico, ese serializador tiene prioridad sobre uno marcado como Default.
Uso básico
Serializado de un único valor
Para crear un serializador personalizado para un tipo, debe definir un tipo de serializador de punto de entrada que implemente los métodos de serialización necesarios. El tipo de serializador de punto de entrada puede ser una clase static o struct, y debe marcarse con CustomMarshallerAttribute.
Por ejemplo, considere un tipo simple que desea serializar entre código administrado y no administrado.
public struct Example
{
public string Message;
public int Flags;
}
Definir el tipo de serializador
Puede crear un tipo denominado ExampleMarshaller marcado con CustomMarshallerAttribute para indicar que es el tipo de serializador de punto de entrada que proporciona información de serialización personalizada para el tipo Example. El primer argumento de CustomMarshallerAttribute es el tipo administrado al que se dirige el serializador. El segundo argumento es el MarshalMode que admite el serializador. El tercer argumento es el propio tipo de serializador, es decir, el tipo que implementa los métodos en la forma esperada.
[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;
}
}
El ExampleMarshaller que se muestra aquí implementa el transporte sin estado del tipo Example administrado a una representación intercalada en el formato que el código nativo espera (ExampleUnmanaged) y de vuelta. El método Free se usa para liberar los recursos no administrados asignados durante el proceso de serializado. La lógica de serialización se controla completamente mediante la implementación del serializador. Marcar campos en una estructura con MarshalAsAttribute no tiene ningún efecto en el código generado.
Aquí, ExampleMarshaller es el tipo de punto de entrada y el tipo de implementación. Sin embargo, si es necesario, puedes personalizar la serialización para distintos modos creando tipos de serializador independientes para cada modo. Agregue un nuevo CustomMarshallerAttribute para cada modo como en la clase siguiente. Normalmente, esto solo es necesario para los serializadores con estado, donde el tipo de serializador es un struct que mantiene el estado entre llamadas. Según la convención, los tipos de implementación se anidan dentro del tipo delegado de punto de entrada.
[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();
}
}
Declara qué despachador usar
Una vez que haya creado el tipo de marcador, puede usar el MarshalUsingAttribute en la firma del método de interoperabilidad para indicar que desea usar este marcador para un parámetro específico o un valor de retorno. MarshalUsingAttribute toma el tipo de serializador de punto de entrada como argumento, en este caso ExampleMarshaller.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ExampleMarshaller))]
internal static partial Example ConvertExample(
[MarshalUsing(typeof(ExampleMarshaller))] Example example);
Para evitar tener que especificar el tipo de serializador para cada uso del tipo Example, también puede aplicar NativeMarshallingAttribute al tipo Example. Esto indica que el marshaller especificado debe usarse de forma predeterminada para todos los usos del tipo Example en la generación de código de interoperabilidad.
[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
public string Message;
public int Flags;
}
A continuación, el tipo Example se puede usar en métodos P/Invoke generados por origen sin especificar el tipo de mariscal. En el siguiente ejemplo de P/Invoke, ExampleMarshaller se utilizará para convertir el parámetro desde 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 utilizar un marshaller diferente para un parámetro específico o el valor de retorno del tipo Example, especifique MarshalUsingAttribute en el lugar de uso donde se aplique. En el siguiente ejemplo de P/Invoke, ExampleMarshaller se utilizará para convertir el parámetro desde 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 de serialización
Colecciones no genéricas
Para las colecciones que no son genéricas sobre el tipo del elemento, debería crear un tipo de serializador simple como se mostró anteriormente.
Colecciones genéricas
Para crear un marshaller personalizado para un tipo de colección genérico, puede usar el atributo ContiguousCollectionMarshallerAttribute. Este atributo indica que el serializador es para colecciones contiguas, como matrices o listas, y proporciona un conjunto de métodos que el serializador debe implementar para admitir la serialización de los elementos de la colección. El tipo de elemento de la colección serializada también debe tener un serializador definido para ella mediante los métodos descritos anteriormente.
Use ContiguousCollectionMarshallerAttribute en un tipo de punto de entrada del serializador para indicar que es para colecciones contiguas. El tipo de entrada del serializador 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 y el generador de origen lo 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 código siguiente, ListMarshaller es el punto de entrada y la implementación. Se ajusta a una de las formas de serialización esperadas de la serialización personalizada de una colección. (Tenga en cuenta que es un ejemplo incompleto).
[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);
}
}
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 marcar el contenedor de recopilación del parámetro de administrado a no administrado y para marcar el contenedor de recopilación para el valor devuelto de no administrado a administrado. El generador de origen generará código para copiar los elementos del parámetro list al contenedor proporcionado por el serializador. Puesto que int puede transferirse en bloque de blits, no es necesario serializar los propios elementos. CountElementName indica que el parámetro numValues debe utilizarse como conteo de elementos al trasladar el valor devuelto de no gestionado a gestionado.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))]
internal static partial List<int> ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
out int numValues);
Cuando el tipo de elemento de la colección es un tipo personalizado, puede especificar el serializador de elementos para que use un elemento adicional MarshalUsingAttribute con ElementIndirectionDepth = 1.
ListMarshaller controlará el contenedor de recopilación y ExampleMarshaller manejará cada elemento de no administrado a administrado y viceversa. ElementIndirectionDepth indica que la operación de serialización debe aplicarse a los elementos de la colección, que se encuentran a un nivel más profundo que la propia colección.
[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);