Compartilhar via


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);

Confira também