Compartilhar via


Geração de origem para invocações de plataforma

O .NET 7 apresenta um gerador de origem para P/Invokes que reconhece o LibraryImportAttribute no código em C#.

Quando não está usando a geração de origem, o sistema de interoperabilidade interno no runtime do .NET gera um stub de IL (linguagem intermediária) – um fluxo de instruções de IL do tipo JIT (just-in-time) – no tempo de execução para facilitar a transição de gerenciado para não gerenciado. O código a seguir mostra como definir e chamar um P/Invoke que use esse mecanismo:

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

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

O stub de IL lida com o marshalling de parâmetros e valores retornados e com a chamada do código não gerenciado, sempre respeitando as configurações em DllImportAttribute que afetam a forma como o código não gerenciado deve ser invocado (por exemplo, SetLastError). Como esse stub de IL é gerado no tempo de execução, ele não está disponível para cenários de aparador AOT (antecipado) ou IL. A geração de IL representa um custo importante do marshalling a ser considerado. Esse custo pode ser medido em termos de desempenho de aplicativo e suporte de plataformas de destino potenciais que podem não permitir a geração dinâmica de código. O modelo de aplicativo AOT Nativo lida com problemas com a geração de código dinâmico fazendo a pré-compilação do código inteiro antecipadamente e diretamente no código nativo. O uso de DllImport não é uma opção para plataformas que exigem cenários de AOT Nativos completos, portanto, é mais apropriado usar outras abordagens (por exemplo, geração de origem). Depurar a lógica de marshalling em cenários DllImport também é uma prática interessante.

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

Uso básico

O LibraryImportAttribute foi projetado para ter um uso semelhante a DllImportAttribute. Podemos converter o exemplo anterior para usar a geração de origem do P/Invoke usando 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 origem será disparado para gerar uma implementação do método ToLower que lida com o marshalling do parâmetro string e o valor retornado como UTF-16. Como o marshalling agora é gerado como código-fonte, é possível examinar e percorrer a lógica em um depurador.

MarshalAs

O gerador de origem 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);

Não há suporte para algumas configurações do MarshalAsAttribute. O gerador de origem emitirá um erro se você tentar usar configurações sem suporte. Para obter mais informações, confira Diferenças do 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

O LibraryImportAttribute destina-se a ser uma conversão simples de DllImportAttribute na maioria dos casos, mas há algumas alterações intencionais:

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

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

Confira também