Geração de código-fonte para invocações de plataforma

O .NET 7 introduz um gerador de código-fonte para P/Invokes que reconhece o LibraryImportAttribute código em C#.

Quando não está usando a geração de origem, o sistema de interoperabilidade interno no tempo de execução do .NET gera um stub de IL — um fluxo de instruções de IL que é JIT — em tempo de execução para facilitar a transição de gerenciado para não gerenciado. O código a seguir mostra a definição e, em seguida, chamando um P/Invoke que usa esse mecanismo:

[DllImport(
    "nativelib",
    EntryPoint = "to_lower",
    CharSet = CharSet.Unicode)]
internal static extern string ToLower(string str);

// string lower = ToLower("StringToConvert");

O stub IL lida com a organização de parâmetros e valores de retorno e chama o código não gerenciado, respeitando as configurações que DllImportAttribute afetam como o código não gerenciado deve ser invocado (por exemplo, SetLastError). Como esse stub de IL é gerado em tempo de execução, ele não está disponível para compiladores antecipados (AOT) ou cenários de corte de IL. A geração da IL representa um custo importante a considerar para o marshaling. Esse custo pode ser medido em termos de desempenho do aplicativo e suporte para potenciais plataformas de destino que podem não permitir a geração de código dinâmico. O modelo de aplicativo AOT nativo aborda problemas com a geração de código dinâmico pré-compilando todo o código com antecedência diretamente no código nativo. Usar DllImport não é uma opção para plataformas que exigem cenários completos de AOT nativo e, portanto, usar outras abordagens (por exemplo, geração de origem) é mais apropriado. Depurar a lógica de empacotamento em DllImport cenários também é um exercício não trivial.

O gerador de código-fonte P/Invoke, incluído no SDK do .NET 7 e habilitado por padrão, procura LibraryImportAttribute um static método e partial para acionar a geração de código-fonte em tempo de compilação do código de empacotamento, removendo a necessidade de geração de um stub IL em tempo de execução e permitindo que o P/Invoke seja embutido. Analisadores e fixadores de código também estão incluídos para ajudar na migração do sistema integrado para o gerador de origem e com o uso em geral.

Utilização básica

O LibraryImportAttribute é projetado para ser semelhante ao DllImportAttribute em uso. Podemos converter o exemplo anterior para usar a geração de origem P/Invoke usando o LibraryImportAttribute e marcando o método como partial em vez de extern:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
internal static partial string ToLower(string str);

Durante a compilação, o gerador de código-fonte será acionado para gerar uma implementação do ToLower método que lida com o empacotamento do parâmetro e o string valor de retorno como UTF-16. Como o empacotamento agora é gerado pelo código-fonte, você pode realmente olhar e percorrer a lógica em um depurador.

MarshalAs

O gerador de fonte também respeita o MarshalAsAttribute. O código anterior também pode ser escrito como:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower")]
[return: MarshalAs(UnmanagedType.LPWStr)]
internal static partial string ToLower(
    [MarshalAs(UnmanagedType.LPWStr)] string str);

Algumas configurações MarshalAsAttribute para não são suportadas. O gerador de origem emitirá um erro se você tentar usar configurações não suportadas. Para obter mais informações, consulte Diferenças de DllImport.

Convenção de chamada

Para especificar a convenção de chamada, use UnmanagedCallConvAttribute, por exemplo:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(
    CallConvs = new[] { typeof(CallConvStdcall) })]
internal static partial string ToLower(string str);

Diferenças de DllImport

LibraryImportAttribute destina-se a ser uma conversão direta de na maioria dos DllImportAttribute casos, mas há algumas mudanças intencionais:

  • CallingConvention não tem equivalente em LibraryImportAttribute. UnmanagedCallConvAttribute em vez disso, deve ser utilizado.
  • CharSet (para CharSet) foi substituído por StringMarshalling (para StringMarshalling). ANSI foi removido e UTF-8 está agora disponível como uma opção de primeira classe.
  • BestFitMapping e ThrowOnUnmappableChar não têm equivalente. Esses campos só eram relevantes ao empacotar uma cadeia de caracteres ANSI no Windows. O código gerado para empacotar uma cadeia de caracteres ANSI terá o comportamento equivalente de BestFitMapping=false e ThrowOnUnmappableChar=false.
  • ExactSpelling não tem equivalente. Este campo era uma configuração centrada no Windows e não tinha efeito em sistemas operacionais que não fossem Windows. O nome do método ou EntryPoint deve ser a ortografia exata do nome do ponto de entrada. Este campo tem usos históricos relacionados com o e W sufixos A usados na programação Win32.
  • PreserveSig não tem equivalente. Este campo era uma configuração centrada no Windows. O código gerado sempre traduz diretamente a assinatura.
  • O projeto deve ser marcado como não seguro usando AllowUnsafeBlocks.

Há também diferenças no suporte para algumas configurações em MarshalAsAttribute, empacotamento padrão de certos tipos e outros atributos relacionados à interoperabilidade. Para obter mais informações, consulte nossa documentação sobre diferenças de compatibilidade.

Consulte também