C# Academy · Lektion

LibraryImport och källgenererad P/Invoke

Använd [LibraryImport] (C# 11+) för AOT-kompatibel, källgenererad marshaling som överträffar DllImport.

Lektion 2 av 412 steg

LibraryImport och källgenererad P/Invoke är en gratis lektion i C# Academy på CoddyKit. Detta är lektion 2 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för C# Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i C# Academy innehåller totalt 4 lektioner.

Varför LibraryImport?

[LibraryImport] introducerades i .NET 7 (C# 11) som en källkodsgenererad, AOT-kompatibel ersättning för [DllImport]. Den klassiska DllImport förlitar sig på körningsbaserad marshalling via reflection, vilket är problematiskt för Native AOT. LibraryImport genererar all marshalling-kod vid kompilering.

Deklarera en LibraryImport-metod

Markera en static partial-metod med [LibraryImport]. Källkodsgeneratorn fyller i implementeringen. Ni måste även markera den omslutande klassen som 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);
}

Strängmarshalling i LibraryImport

Till skillnad från DllImport kräver LibraryImport att ni uttryckligen anger strängmarshalling. Ni kan använda enumen StringMarshalling eller attribut med MarshalAs, så att marshalling-kostnaden blir synlig och kontrollerbar.

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

Strukturmarshalling

För strukturer lägger ni till [NativeMarshalling] för att definiera hur den hanterade typen mappas till sin native-representation. Källkodsgeneratorn använder marshaller-typen för att generera säker kod som minimerar allokeringar.

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

Jämförelse mellan DllImport och LibraryImport

DllImport tolkas vid körning – långsam uppstart, reflection-baserad och inkompatibel med AOT-trimning. LibraryImport genererar optimerad C#-kod vid byggtillfället: ingen reflection vid körning, trimningssäker och mätbart snabbare i benchmarktester.

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

Ange SetLastError = true i [LibraryImport] för att fånga operativsystemets felkod. Använd Marshal.GetLastPInvokeError() (rekommenderas) eller Marshal.GetLastWin32Error() för att hämta den efter anropet.

[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> och minnesmarshalling

En av fördelarna med källkodsgenererad P/Invoke är förstklassigt stöd för Span<T>. Att skicka en ReadOnlySpan<byte> undviker pinning och allokering som DllImport med arrayer skulle kräva.

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

Aktivera källkodsgenerering

Källkodsgenerering aktiveras automatiskt när ni refererar till namnrymden System.Runtime.InteropServices i ett projekt som riktar sig mot .NET 7 eller senare. För äldre målversioner behöver ni analyspaketet 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>

Granska genererad kod

Lägg till <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> i csproj-filen för att skriva genererade filer till obj/. Då kan ni granska exakt vad källkodsgeneratorn producerar – en utmärkt lärorik övning.

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

I praktiken: omsluta ett C-bibliotek

Ett vanligt mönster är att definiera en statisk wrapper-klass med alla LibraryImport-deklarationer och sedan exponera ett säkert API på hög nivå ovanpå. Håll de partial-deklarationerna interna eller privata och exponera endast de säkra wrapper-metoderna offentligt.

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

Snabb kontroll

Vilken är den främsta fördelen med [LibraryImport] jämfört med [DllImport]?

Sammanfattning: LibraryImport och källkodsgenererad P/Invoke

Viktigaste punkterna:

  • [LibraryImport] ersätter [DllImport] för AOT-säker P/Invoke
  • Marshalling genereras vid kompilering – ingen reflection vid körning
  • Kräver en static partial-metod och en partial-klass
  • Strängmarshalling måste anges uttryckligen via StringMarshalling eller MarshalAs
  • Förstklassigt stöd för Span<T> utan kostnaden för pinning
  • Granska genererad kod med EmitCompilerGeneratedFiles
Gratis att börja

Lär dig C# med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
93
Lektioner
346

Vanliga frågor

Är lektionen ”LibraryImport och källgenererad P/Invoke” gratis?

Ja – hela texten till ”LibraryImport och källgenererad P/Invoke” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i C# Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i C# Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”LibraryImport och källgenererad P/Invoke”?

Använd [LibraryImport] (C# 11+) för AOT-kompatibel, källgenererad marshaling som överträffar DllImport. Ni övar på C# Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig C# Academy?

Du behöver inga förkunskaper. Utbildningen i C# Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 4.

Hur lång tid tar lektionen ”LibraryImport och källgenererad P/Invoke”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här C# Academy-lektionen?

Ja. Varje C# Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Grunderna i P/Invoke
  2. LibraryImport och källgenererad P/Invoke
  3. Osäker kod, pekare och fasta buffertar
  4. COM-interoperabilitet och Runtime Callable Wrappers
← Tillbaka till C# Academy