Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
O .NET 7 introduz um novo mecanismo para personalização de como um tipo é manipulado ao usar a interoperação gerada a partir de código-fonte. O gerador de origem para P/Invokes reconhece MarshalUsingAttribute e NativeMarshallingAttribute como indicadores para empacotamento personalizado de um tipo.
NativeMarshallingAttribute pode ser aplicado a um tipo para indicar o empacotamento personalizado padrão para esse tipo. O MarshalUsingAttribute pode ser aplicado a um parâmetro ou valor de retorno para indicar o empacotamento personalizado para essa utilização específica do tipo, tendo precedência sobre qualquer NativeMarshallingAttribute que possa estar no próprio tipo. Ambos os atributos esperam um Type—o tipo marshaller de ponto de entrada—que é marcado com um ou mais CustomMarshallerAttribute atributos. Cada CustomMarshallerAttribute indica qual implementação de marshaller deve ser usada para serializar o tipo gerenciado especificado no MarshalMode.
Implementação do Marshaller
As implementações de marshaller personalizado podem ser sem estado ou com estado. Se o tipo marshaller for uma static classe, ele será considerado sem monitoração de estado, e os métodos de implementação não devem rastrear o estado nas chamadas. Se for um tipo de valor, ele é considerado com estado, e uma instância desse marshaller será usada para processar um parâmetro específico ou valor de retorno. O uso de uma instância única permite que o estado seja preservado durante todo o processo de empacotamento e desempacotamento.
Formatos de Marshaller
O conjunto de métodos que o gerador de marshalling espera de um tipo de marshaller personalizado é referido como a estrutura do marshaller. Para oferecer suporte a tipos de marshaller personalizados estáticos e sem estado no .NET Standard 2.0 (que não oferece suporte a métodos de interface estática) e melhorar o desempenho, não são usados os tipos de interface para definir e implementar as estruturas dos marshallers. Em vez disso, as formas são descritas no artigo Formas de marshaller personalizadas. Os métodos (ou forma) esperados dependem se o marshaller é stateless ou stateful, e se ele suporta marshalling de gerenciado para não gerenciado, não gerenciado para gerenciado, ou ambos (declarado com CustomMarshallerAttribute.MarshalMode). O SDK do .NET inclui analisadores e fixadores de código para ajudar na implementação de marshallers que estejam em conformidade com as formas necessárias.
MarshalMode
O MarshalMode especificado num CustomMarshallerAttribute determina o suporte de marshalling esperado e a forma para a implementação do marshaller. Todos os modos suportam implementações de marshaller sem estado. Os modos de empacotamento de elementos não suportam implementações de marshaller com estado.
MarshalMode |
Apoio esperado | Pode ser com estado (stateful) |
|---|---|---|
| ManagedToUnmanagedIn | Conseguiu não ser gerenciado | Sim |
| ManagedToUnmanagedRef | Gerenciado para não gerenciado e não gerenciado para gerenciado | Sim |
| ManagedToUnmanagedOut | Não gerenciado para gerenciar | Sim |
| UnmanagedToManagedIn | Não gerenciado para gerenciar | Sim |
| UnmanagedToManagedRef | Gerenciado para não gerenciado e não gerenciado para gerenciado | Sim |
| UnmanagedToManagedOut | Conseguiu não ser gerenciado | Sim |
| ElementIn | Conseguiu não ser gerenciado | Não |
| ElementRef | Gerenciado para não gerenciado e não gerenciado para gerenciado | Não |
| ElementOut | Não gerenciado para gerenciar | Não |
Use MarshalMode.Default para indicar que a implementação do marshaller se aplica a qualquer modo suportado, com base nos métodos que implementa. Se você especificar um marshaller para um mais específico MarshalMode, esse marshaller terá precedência sobre um marcado como Default.
Utilização básica
Encaminhar um único valor
Para criar um marshaller personalizado para um tipo, é necessário definir um tipo de marshaller de ponto de entrada que implemente os métodos de marshalling necessários. O tipo de marshaler de entrada pode ser uma classe static ou um struct, e deve ser marcado com CustomMarshallerAttribute.
Por exemplo, considere um tipo simples que você deseja organizar entre código gerenciado e não gerenciado:
public struct Example
{
public string Message;
public int Flags;
}
Definir o tipo de marshaller
Você pode criar um tipo chamado ExampleMarshaller marcado com CustomMarshallerAttribute para indicar que é o tipo marshalizador de ponto de entrada que fornece informações de empacotamento personalizadas para o tipo Example. O primeiro argumento de CustomMarshallerAttribute é o tipo gerenciado que o marshaller processa. O segundo argumento é o MarshalMode que o marshaller suporta. O terceiro argumento é o próprio tipo marshaller, ou seja, o tipo que implementa os métodos no formato esperado.
[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;
}
}
O ExampleMarshaller mostrado aqui implementa o marshalling sem estado do tipo gerenciado Example para uma representação blittable no formato que o código nativo espera (ExampleUnmanaged) e vice-versa. O Free método é usado para liberar quaisquer recursos não gerenciados alocados durante o processo de empacotamento. A lógica de marshalling é inteiramente controlada pela implementação do marshaller. A marcação de campos em uma struct com MarshalAsAttribute não tem efeito sobre o código gerado.
Aqui, ExampleMarshaller é o tipo de ponto de entrada e o tipo de implementação. No entanto, se necessário, você pode personalizar o marshalling para diferentes modos, criando tipos de marshaller separados para cada modo. Adicione um novo CustomMarshallerAttribute para cada modo, como na classe a seguir. Normalmente, isso só é necessário para marshallers com estado, onde o tipo marshaller é um struct que mantém estado ao longo das chamadas. Por convenção, os tipos de implementação são aninhados dentro do tipo marshaller de ponto 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();
}
}
Declarar qual marshaller utilizar
Depois de criar o tipo de marshaller, pode usar MarshalUsingAttribute na assinatura do método de interoperabilidade para indicar que deseja usar este marshaller para um parâmetro específico ou valor de retorno. O MarshalUsingAttribute utiliza o tipo marshaller de ponto de entrada como argumento, neste caso ExampleMarshaller.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ExampleMarshaller))]
internal static partial Example ConvertExample(
[MarshalUsing(typeof(ExampleMarshaller))] Example example);
Para evitar ter de especificar o tipo de marshaller para cada uso do tipo Example, poderá também aplicar o NativeMarshallingAttribute diretamente ao tipo Example. Isso indica que o marshaller especificado deve ser usado por padrão em todos os usos do tipo Example na geração de código de interoperabilidade.
[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
public string Message;
public int Flags;
}
O Example tipo pode então ser usado em métodos P/Invoke gerados pela fonte sem especificar o tipo de marshaller. No exemplo P/Invoke a seguir, ExampleMarshaller será usado para marshalar o parâmetro de managed para unmanaged. Ele também será usado para converter o valor de retorno de não-gerenciado para gerenciado.
[LibraryImport("nativelib")]
internal static partial Example ConvertExample(Example example);
Para usar um marshaller diferente para um parâmetro ou valor de retorno específico do tipo Example, especifique MarshalUsingAttribute no local de uso. No exemplo P/Invoke a seguir, ExampleMarshaller será usado para marshalar o parâmetro de managed para unmanaged.
OtherExampleMarshaller será usado para organizar o valor de retorno de não gerenciado para gerenciado.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(OtherExampleMarshaller))]
internal static partial Example ConvertExample(Example example);
Marshalling coleções
Coleções não genéricas
Para coleções que não são genéricas em relação ao tipo de elemento, deve-se criar um tipo de marshaller simples, como mostrado anteriormente.
Coleções genéricas
Para criar um marshaller personalizado para um tipo de coleção genérico, você pode usar o ContiguousCollectionMarshallerAttribute atributo. Este atributo indica que o marshaller é para coleções contíguas, como matrizes ou listas, e fornece um conjunto de métodos que o marshaller deve implementar para suportar o processo de marshalling dos elementos da coleção. O tipo de elemento da coleção que é marshalado deve também ter um marshaller definido, utilizando os métodos descritos anteriormente.
Aplique o ContiguousCollectionMarshallerAttribute a um tipo de ponto de entrada de marshaller para indicar que é utilizado para coleções contíguas. O tipo de ponto de entrada marshaller deve ter um parâmetro de tipo a mais do que o tipo gerenciado associado. O último parâmetro type é 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. Está em conformidade com uma das formas de marshaller esperadas para o empacotamento personalizado de uma coleção. (Observe que é um exemplo 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);
}
}
O ListMarshaller no exemplo é um marshaller de coleção apátrida que implementa suporte para o marshaling de gestão para não gestão e de não gestão para gestão para um List<T>. No exemplo P/Invoke a seguir, ListMarshaller será usado para empacotar o contêiner de coleta para o parâmetro de gerenciado para não gerenciado e para organizar o contêiner de coleta para o valor de retorno de não gerenciado para gerenciado. O gerador fonte irá gerar código para copiar os elementos do parâmetro list para o recipiente fornecido pelo marshaller. Uma vez que int é conversível diretamente, os elementos em si não precisam ser transformados.
CountElementName indica que o numValues parâmetro deve ser usado como a contagem de elementos ao organizar o valor de retorno de não gerenciado para gerenciado.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))]
internal static partial List<int> ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
out int numValues);
Quando o tipo de elemento da coleção é um tipo personalizado, pode especificar o marshaller de elementos para isso utilizando um MarshalUsingAttribute adicional juntamente com ElementIndirectionDepth = 1.
O ListMarshaller gerenciará o container de coleção e ExampleMarshaller converterá cada elemento de não gerenciado para gerenciado e vice-versa. O ElementIndirectionDepth indica que o marshaller deve ser aplicado aos elementos da coleção, que estão um nível abaixo da coleção propriamente dita.
[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);