0Pricing
C# Academy · Aula

LibraryImport e P/Invoke gerado pela fonte

Use [LibraryImport] (C# 11+) para obter marshaling compatível com AOT e gerado pela fonte, com desempenho superior ao de DllImport.

LibraryImport e P/Invoke gerado pela fonte é uma aula grátis de C# Academy no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de C# Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de C# Academy inclui 4 aulas no total.

Por que usar LibraryImport?

[LibraryImport] foi introduzido no .NET 7 (C# 11) como uma alternativa compatível com AOT e gerada pelo código-fonte para [DllImport]. O DllImport clássico depende da conversão em tempo de execução por meio de reflexão — algo problemático para o Native AOT. LibraryImport gera todo o código de conversão no momento da compilação.

Declarando um método LibraryImport

Marque um método static partial com [LibraryImport]. O gerador de código-fonte preenche a implementação. Também é necessário marcar a classe que o contém como partial.

using System.Runtime.InteropServices;

internal static partial class NativeMethods
{
    // Source generator creates the P/Invoke body at compile time
    [LibraryImport("mylib", EntryPoint = "add_integers")]
    internal static partial int AddIntegers(int a, int b);

    // String marshaling must be explicit in LibraryImport
    [LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
    internal static partial int ProcessString(string text);
}

Conversão de strings em LibraryImport

Ao contrário de DllImport, LibraryImport exige que você especifique explicitamente a conversão de strings. É possível usar a enumeração StringMarshalling ou atributos MarshalAs, tornando o custo dessa conversão visível e controlável.

// Option 1: StringMarshalling enum (all strings in method)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16)]
static partial bool CreateDirectory(string lpPathName, nint lpSecurityAttributes);

// Option 2: Per-parameter MarshalAs
[LibraryImport("libc")]
static partial int Open(
    [MarshalAs(UnmanagedType.LPUTF8Str)] string path,
    int flags);

// Option 3: MarshalUsing for custom marshalers
[LibraryImport("mylib")]
static partial void Process(
    [MarshalUsing(typeof(Utf8StringMarshaller))] string name);

Conversão de estruturas

Para estruturas, adicione [NativeMarshalling] para definir como o tipo gerenciado é mapeado para sua representação nativa. O gerador de código-fonte usa o tipo conversor para gerar código seguro que minimiza alocações.

[NativeMarshalling(typeof(PointMarshaller))]
public struct Point
{
    public int X;
    public int Y;
}

[CustomMarshaller(typeof(Point), MarshalMode.Default, typeof(PointMarshaller))]
public static class PointMarshaller
{
    public static Point ConvertToManaged(NativePoint native)
        => new Point { X = native.X, Y = native.Y };
    public static NativePoint ConvertToUnmanaged(Point managed)
        => new NativePoint { X = managed.X, Y = managed.Y };

    [StructLayout(LayoutKind.Sequential)]
    public struct NativePoint { public int X, Y; }
}

Comparando DllImport e LibraryImport

DllImport é interpretado em tempo de execução — inicialização lenta, baseado em reflexão e incompatível com a remoção de código não utilizado no AOT. LibraryImport gera código C# otimizado no momento da compilação: nenhuma reflexão em tempo de execução, segurança contra remoção de código e desempenho comprovadamente superior nos testes de desempenho.

// Old — DllImport (still works, but avoid for new AOT code)
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool MoveFile(string src, string dst);

// New — LibraryImport (AOT-safe, source-generated)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
                SetLastError = true)]
static partial bool MoveFile(string src, string dst);
// The compiler generates the actual P/Invoke wrapper body

Definindo SetLastError e tratando erros

Defina SetLastError = true em [LibraryImport] para capturar o código de erro do sistema operacional. Use Marshal.GetLastPInvokeError() (preferencialmente) ou Marshal.GetLastWin32Error() para recuperá-lo após a chamada.

[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
                SetLastError = true)]
static partial bool CreateDirectory(string path, nint secAttr);

bool ok = CreateDirectory(@"C:\Temp\NewDir", 0);
if (!ok)
{
    int err = Marshal.GetLastPInvokeError();
    // err is ERROR_ALREADY_EXISTS (183) if folder exists
    throw new Win32Exception(err);
}

Span<T> e conversão de memória

Uma das vantagens do P/Invoke gerado pelo código-fonte é o suporte nativo a Span<T>. Passar um ReadOnlySpan<byte> evita a fixação e a alocação que seriam necessárias com matrizes usadas por DllImport.

[LibraryImport("mylib")]
static partial int ProcessBuffer(
    ReadOnlySpan<byte> data,
    int length);

// Usage — no fixed or GCHandle needed
byte[] buffer = Encoding.UTF8.GetBytes("hello");
int result = ProcessBuffer(buffer, buffer.Length);

// For output buffers use Span<byte>
[LibraryImport("mylib")]
static partial int FillBuffer(Span<byte> output, int maxLen);

Habilitando a geração de código-fonte

A geração de código-fonte é habilitada automaticamente quando você faz referência ao namespace System.Runtime.InteropServices em um projeto destinado ao .NET 7+. Para destinos mais antigos, você precisa do pacote de analisador Microsoft.Interop.SourceGeneration.

<!-- In your .csproj — no extra package needed on .NET 7+ -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <!-- Enable Roslyn analyzers and source generators -->
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <!-- AllowUnsafeBlocks may be needed for some marshalers -->
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
</Project>

Inspecionando o código gerado

Adicione <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> ao seu csproj para gravar os arquivos gerados em obj/. Isso permite inspecionar exatamente o que o gerador de código-fonte produz — um ótimo exercício de aprendizagem.

// The generator emits something like this for a LibraryImport method:
// (simplified view of generated stub)

internal static partial int AddIntegers(int a, int b)
{
    // Direct call — no reflection, no boxing, fully inlineable
    return __PInvoke(a, b);

    [System.Runtime.InteropServices.DllImportAttribute(
        "mylib",
        EntryPoint = "add_integers",
        ExactSpelling = true)]
    static extern int __PInvoke(int a, int b);
}

Exemplo real: encapsulando uma biblioteca C

Um padrão comum é definir uma classe estática de encapsulamento com todas as declarações LibraryImport e, depois, expor uma API segura de alto nível. Mantenha as declarações partial internas/privadas e exponha publicamente apenas os wrappers seguros.

internal static partial class LibSodiumNative
{
    private const string LibName = "libsodium";

    [LibraryImport(LibName, EntryPoint = "crypto_secretbox_keybytes")]
    internal static partial nuint KeyBytes();

    [LibraryImport(LibName, EntryPoint = "crypto_secretbox_easy")]
    internal static partial int SecretBoxEasy(
        Span<byte> ciphertext,
        ReadOnlySpan<byte> message,
        ulong mlen,
        ReadOnlySpan<byte> nonce,
        ReadOnlySpan<byte> key);
}

// Public safe wrapper hides the native signature
public static byte[] Encrypt(byte[] message, byte[] key, byte[] nonce)
{
    var cipher = new byte[message.Length + 16];
    LibSodiumNative.SecretBoxEasy(cipher, message, (ulong)message.Length, nonce, key);
    return cipher;
}

Verificação rápida

Qual é a principal vantagem de [LibraryImport] em relação a [DllImport]?

Recapitulação: P/Invoke gerado pelo código-fonte com LibraryImport

Principais conclusões:

  • [LibraryImport] substitui [DllImport] para P/Invoke seguro para AOT
  • A conversão é gerada no momento da compilação — sem reflexão em tempo de execução
  • Exige um método static partial e uma classe partial
  • A conversão de strings deve ser explícita por meio de StringMarshalling ou MarshalAs
  • Suporte nativo a Span<T> sem o custo da fixação
  • Inspecione o código gerado com EmitCompilerGeneratedFiles

Perguntas Frequentes

A aula “LibraryImport e P/Invoke gerado pela fonte” é grátis?

Sim — o texto completo de “LibraryImport e P/Invoke gerado pela fonte” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de C# Academy, atualize para CoddyKit PRO. O curso de C# Academy inclui 4 aulas no total.

O que vou aprender em “LibraryImport e P/Invoke gerado pela fonte”?

Use [LibraryImport] (C# 11+) para obter marshaling compatível com AOT e gerado pela fonte, com desempenho superior ao de DllImport. Você pratica C# Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar C# Academy?

Nenhuma experiência prévia é necessária. C# Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.

Quanto tempo leva a aula “LibraryImport e P/Invoke gerado pela fonte”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de C# Academy?

Sim. Cada aula de C# Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Fundamentos de P/Invoke
  2. LibraryImport e P/Invoke gerado pela fonte
  3. Código não seguro, ponteiros e buffers fixos
  4. Interoperação com COM e wrappers chamáveis em tempo de execução
← Voltar para C# Academy