0Pricing
C# Academy · Lezione

LibraryImport e P/Invoke generato dal codice sorgente

Usi [LibraryImport] (C# 11+) per ottenere un marshaling compatibile con AOT e generato dal codice sorgente, più efficiente di DllImport.

LibraryImport e P/Invoke generato dal codice sorgente è una lezione C# Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento C# Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso C# Academy include 4 lezioni in totale.

Perché usare LibraryImport

[LibraryImport] è stato introdotto in .NET 7 (C# 11) come sostituto compatibile con AOT e basato sulla generazione del codice sorgente di [DllImport]. Il classico DllImport si basa sul marshalling a runtime tramite reflection, una soluzione problematica per Native AOT. LibraryImport genera tutto il codice di marshalling in fase di compilazione.

Dichiarare un metodo LibraryImport

Contrassegnare un metodo static partial con [LibraryImport]. Il generatore di codice sorgente ne completa l'implementazione. È inoltre necessario contrassegnare come partial la classe che lo contiene.

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);
}

Marshalling delle stringhe con LibraryImport

A differenza di DllImport, LibraryImport richiede di specificare esplicitamente il marshalling delle stringhe. È possibile usare l'enum StringMarshalling o gli attributi MarshalAs, rendendo il costo del marshalling visibile e controllabile.

// 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);

Marshalling delle struct

Per le struct, aggiungere [NativeMarshalling] per definire la mappatura del tipo gestito alla relativa rappresentazione nativa. Il generatore di codice sorgente usa il tipo marshaller per produrre codice sicuro che riduce al minimo le allocazioni.

[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; }
}

Confronto tra DllImport e LibraryImport

DllImport viene interpretato a runtime: avvio lento, uso della reflection e incompatibilità con il trimming AOT. LibraryImport genera codice C# ottimizzato in fase di compilazione: nessuna reflection a runtime, sicurezza rispetto al trimming e prestazioni misurabilmente migliori nei benchmark.

// 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

SetLastError e gestione degli errori

Impostare SetLastError = true in [LibraryImport] per acquisire il codice di errore del sistema operativo. Usare Marshal.GetLastPInvokeError() (scelta preferibile) o Marshal.GetLastWin32Error() per recuperarlo dopo la chiamata.

[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);
}

Marshalling di Span<T> e della memoria

Uno dei vantaggi di P/Invoke con generazione del codice sorgente è il supporto nativo a Span<T>. Passare un ReadOnlySpan<byte> evita il pinning e l'allocazione che sarebbero necessari usando array con 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);

Abilitare la generazione del codice sorgente

La generazione del codice sorgente viene abilitata automaticamente quando si fa riferimento al namespace System.Runtime.InteropServices in un progetto destinato a .NET 7 o versioni successive. Per le destinazioni precedenti è necessario il pacchetto analyzer 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>

Esaminare il codice generato

Aggiungere <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> al file csproj per scrivere i file generati in obj/. In questo modo è possibile esaminare esattamente ciò che produce il generatore di codice sorgente: un ottimo esercizio per imparare.

// 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);
}

Caso reale: creare un wrapper per una libreria C

Un approccio comune consiste nel definire una classe wrapper statica contenente tutte le dichiarazioni LibraryImport, quindi esporre al di sopra un'API sicura di livello superiore. Mantenere le dichiarazioni partial interne/private ed esporre pubblicamente solo i wrapper sicuri.

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 rapida

Qual è il principale vantaggio di [LibraryImport] rispetto a [DllImport]?

Riepilogo: LibraryImport e P/Invoke con generazione del codice sorgente

Concetti chiave:

  • [LibraryImport] sostituisce [DllImport] per un P/Invoke sicuro con AOT
  • Il marshalling viene generato in fase di compilazione: nessuna reflection a runtime
  • Richiede un metodo static partial e una classe partial
  • Il marshalling delle stringhe deve essere esplicito tramite StringMarshalling o MarshalAs
  • Supporto nativo a Span<T> senza l'overhead del pinning
  • Esaminare il codice generato con EmitCompilerGeneratedFiles

Domande Frequenti

La lezione «LibraryImport e P/Invoke generato dal codice sorgente» è gratuita?

Sì — il testo completo di «LibraryImport e P/Invoke generato dal codice sorgente» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso C# Academy, passa a CoddyKit PRO. Il corso C# Academy include 4 lezioni in totale.

Cosa imparerò in «LibraryImport e P/Invoke generato dal codice sorgente»?

Usi [LibraryImport] (C# 11+) per ottenere un marshaling compatibile con AOT e generato dal codice sorgente, più efficiente di DllImport. Eserciti C# Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare C# Academy?

Non è richiesta alcuna esperienza precedente. C# Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «LibraryImport e P/Invoke generato dal codice sorgente»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione C# Academy?

Sì. Ogni lezione C# Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Fondamenti di P/Invoke
  2. LibraryImport e P/Invoke generato dal codice sorgente
  3. Codice non sicuro, puntatori e buffer fissi
  4. Interop COM e wrapper richiamabili dal runtime
← Torna a C# Academy