Geração de origem para marshalling personalizado
O .NET 7 apresenta um novo mecanismo para personalização de como o marshalling de um tipo é feito ao usar a interoperabilidade gerada pela origem. O gerador de origem para P/Invokes reconhece MarshalUsingAttribute e NativeMarshallingAttribute como indicadores para o marshalling personalizado de um tipo.
NativeMarshallingAttribute pode ser aplicado a um tipo para indicar o marshalling personalizado padrão para esse tipo. O MarshalUsingAttribute pode ser aplicado a um parâmetro ou valor retornado para indicar o marshalling personalizado para esse uso específico do tipo, tendo precedência sobre qualquer NativeMarshallingAttribute que possa estar no próprio tipo. Esses dois atributos esperam um Type – o tipo de marshaller do ponto de entrada –, que é marcado com um ou mais atributos CustomMarshallerAttribute. Cada CustomMarshallerAttribute indica qual implementação do marshaller deve ser usada para realizar marshalling do tipo gerenciado especificado para o MarshalMode especificado.
Implementação do marshaller
As implementações do marshaller podem ser sem estado ou com estado. Se o tipo do marshaller for uma classe static
, ele será considerado sem estado. Se for um tipo de valor, ele será considerado com estado e uma instância desse marshaller será usada para realizar marshalling de um valor retornado ou parâmetro específico. Diferentes formas para a implementação do marshaller são esperadas dependendo se o marshaller ser sem estado ou com estado e de dar suporte ao marshalling de gerenciado para não gerenciado, não gerenciado para gerenciado ou ambos. O SDK do .NET inclui analisadores e reparadores de código para ajudar a implementar marshallers que estão em conformidade com as formas necessárias.
MarshalMode
O MarshalMode especificado em um CustomMarshallerAttribute determina o suporte de marshalling esperado e a forma para a implementação do marshaller. Todos os modos dão suporte a implementações de marshaller sem estado. Os modos de marshalling dos elementos não dão suporte a implementações de marshaller com estado.
MarshalMode |
Suporte esperado | Pode ser com estado |
---|---|---|
ManagedToUnmanagedIn | Gerenciado para não gerenciado | Sim |
ManagedToUnmanagedRef | Gerenciado para não gerenciado e não gerenciado para gerenciado | Sim |
ManagedToUnmanagedOut | Não gerenciado para gerenciado | Sim |
UnmanagedToManagedIn | Não gerenciado para gerenciado | Sim |
UnmanagedToManagedRef | Gerenciado para não gerenciado e não gerenciado para gerenciado | Sim |
UnmanagedToManagedOut | Gerenciado para não gerenciado | Sim |
ElementIn | Gerenciado para não gerenciado | No |
ElementRef | Gerenciado para não gerenciado e não gerenciado para gerenciado | No |
ElementOut | Não gerenciado para gerenciado | No |
MarshalMode.Default indica que a implementação do marshaller deve ser usada para qualquer modo compatível. Se uma implementação de marshaller para um MarshalMode
mais específico também for especificada, ela terá precedência sobre MarshalMode.Default
.
Uso básico
Podemos especificar NativeMarshallingAttribute em um tipo, apontando para um tipo de marshaller de ponto de entrada que é uma classe static
ou um struct
.
[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
public string Message;
public int Flags;
}
ExampleMarshaller
, o tipo de marshaller de ponto de entrada, é marcado com CustomMarshallerAttribute, apontando para um tipo de implementação de marshaller. Neste exemplo, ExampleMarshaller
é o ponto de entrada e a implementação. Ele está em conformidade com as formas de marshaller esperadas para o marshalling personalizado de um 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;
}
}
O ExampleMarshaller
no exemplo é um marshaller sem estado que implementa o suporte para marshalling de gerenciado para não gerenciado e de não gerenciado para gerenciado. A lógica de marshalling é totalmente controlada pela implementação do marshaller. Marcar campos em um struct com MarshalAsAttribute não tem efeito sobre o código gerado.
O tipo Example
pode, então, ser usado na geração de origem P/Invoke. No exemplo de P/Invoke a seguir, ExampleMarshaller
será usado para realizar marshaling do parâmetro de gerenciado para não gerenciado. Ele também será usado para realizar marshaling do valor retornado de não gerenciado para gerenciado.
[LibraryImport("nativelib")]
internal static partial Example ConvertExample(Example example);
Para usar um marshaller diferente para um uso específico do tipo Example
, especifique MarshalUsingAttribute no site de uso. No exemplo de P/Invoke a seguir, ExampleMarshaller
será usado para realizar marshaling do parâmetro de gerenciado para não gerenciado. OtherExampleMarshaller
será usado para realizar marshaling do valor retornado de não gerenciado para gerenciado.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(OtherExampleMarshaller))]
internal static partial Example ConvertExample(Example example);
Coleções
Aplique o ContiguousCollectionMarshallerAttribute a um tipo de ponto de entrada de marshaller para indicar que ele é para coleções contíguas. O tipo deve ter um parâmetro de tipo a mais do que o tipo gerenciado associado. O último parâmetro de tipo é um espaço reservado e será preenchido pelo gerador de origem com o tipo não gerenciado para o tipo de elemento da coleção.
Por exemplo, você pode especificar o marshalling personalizado para um List<T>. No código a seguir, ListMarshaller
é o ponto de entrada e a implementação. Ele está em conformidade com as formas de marshaller esperadas para o marshalling personalizado de uma coleção.
[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();
}
O ListMarshaller
no exemplo é um marshaller de coleção sem estado que implementa o suporte para marshalling de gerenciado para não gerenciado e de não gerenciado para gerenciado para um List<T>. No exemplo de P/Invoke a seguir, ListMarshaller
será usado para realizar marshaling do parâmetro de gerenciado para não gerenciado e para realizar marshaling do valor retornado de não gerenciado para gerenciado. CountElementName indica que o parâmetro numValues
deve ser usado como a contagem de elementos ao realizar marshaling do valor retornado de não gerenciado para gerenciado.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = "numValues")]
internal static partial void ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
out int numValues);