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
eThrowOnUnmappableChar=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
eW
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.