0Pricing
C# Academy · Lekcja

LibraryImport i generowane P/Invoke

Używaj [LibraryImport] (C# 11+) do generowanego na podstawie kodu źródłowego, zgodnego z AOT marshalingu, który działa wydajniej niż DllImport.

LibraryImport i generowane P/Invoke to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.

Dlaczego LibraryImport?

[LibraryImport] wprowadzono w .NET 7 (C# 11) jako generowaną ze źródeł, zgodną z AOT alternatywę dla [DllImport]. Klasyczne DllImport opiera się na marszalingu wykonywanym w czasie działania za pomocą refleksji, co stwarza problemy w przypadku Native AOT. LibraryImport generuje cały kod marszalingu podczas kompilacji.

Deklarowanie metody LibraryImport

Metodę static partial należy oznaczyć atrybutem [LibraryImport]. Generator kodu źródłowego uzupełni jej implementację. Klasa zawierająca tę metodę również musi być oznaczona jako 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);
}

Marszaling ciągów znaków w LibraryImport

W przeciwieństwie do DllImport, LibraryImport wymaga jawnego określenia sposobu marszalingu ciągów znaków. Można użyć wyliczenia StringMarshalling lub atrybutów MarshalAs, dzięki czemu koszt marszalingu jest widoczny i możliwy do kontrolowania.

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

Marszaling struktur

W przypadku struktur należy dodać [NativeMarshalling], aby określić sposób odwzorowania typu zarządzanego na jego reprezentację natywną. Generator kodu źródłowego używa typu marszalera do wygenerowania bezpiecznego kodu ograniczającego liczbę alokacji.

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

Porównanie DllImport i LibraryImport

DllImport jest interpretowane w czasie działania — powoduje wolniejszy start, opiera się na refleksji i jest niezgodne z przycinaniem AOT. LibraryImport generuje zoptymalizowany kod C# podczas kompilacji: bez refleksji w czasie działania, bezpieczny dla przycinania i mierzalnie szybszy w testach wydajnościowych.

// 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 i obsługa błędów

Ustaw SetLastError = true w [LibraryImport], aby przechwycić kod błędu systemu operacyjnego. Użyj Marshal.GetLastPInvokeError() (zalecane) lub Marshal.GetLastWin32Error(), aby pobrać go po wywołaniu.

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

Marszaling Span<T> i pamięci

Jedną z zalet generowanego ze źródeł P/Invoke jest pełnoprawna obsługa typu Span<T>. Przekazanie ReadOnlySpan<byte> eliminuje konieczność przypinania i alokacji, których wymagałoby użycie tablic z 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);

Włączanie generowania kodu źródłowego

Generowanie kodu źródłowego włącza się automatycznie po odwołaniu do przestrzeni nazw System.Runtime.InteropServices w projekcie przeznaczonym dla platformy .NET 7 lub nowszej. W przypadku starszych platform docelowych potrzebny jest pakiet analizatora 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>

Sprawdzanie wygenerowanego kodu

Dodaj <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> do pliku csproj, aby zapisywać wygenerowane pliki w katalogu obj/. Pozwala to dokładnie sprawdzić, co generuje generator kodu źródłowego — to świetne ćwiczenie edukacyjne.

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

Praktyczny przykład: opakowanie biblioteki C

Typowy wzorzec polega na zdefiniowaniu statycznej klasy opakowującej zawierającej wszystkie deklaracje LibraryImport, a następnie udostępnieniu na jej podstawie bezpiecznego interfejsu wysokiego poziomu. Deklaracje partial należy zachować jako internal/private, a publicznie udostępniać wyłącznie bezpieczne opakowania.

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

Szybkie sprawdzenie

Jaka jest główna zaleta [LibraryImport] w porównaniu z [DllImport]?

Podsumowanie: LibraryImport i P/Invoke z generowaniem kodu źródłowego

Najważniejsze wnioski:

  • [LibraryImport] zastępuje [DllImport] w przypadku bezpiecznego dla AOT P/Invoke
  • Marszaling jest generowany podczas kompilacji — bez refleksji w czasie działania
  • Wymaga metody static partial oraz klasy partial
  • Marszaling ciągów znaków musi być określony jawnie za pomocą StringMarshalling lub MarshalAs
  • Pełnoprawna obsługa Span<T> bez narzutu związanego z przypinaniem
  • Wygenerowany kod można sprawdzić za pomocą EmitCompilerGeneratedFiles

Często zadawane pytania

Czy lekcja „LibraryImport i generowane P/Invoke” jest bezpłatna?

Tak — pełny tekst „LibraryImport i generowane P/Invoke” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „LibraryImport i generowane P/Invoke”?

Używaj [LibraryImport] (C# 11+) do generowanego na podstawie kodu źródłowego, zgodnego z AOT marshalingu, który działa wydajniej niż DllImport. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć C# Academy?

Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „LibraryImport i generowane P/Invoke”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?

Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Podstawy P/Invoke
  2. LibraryImport i generowane P/Invoke
  3. Kod niebezpieczny, wskaźniki i bufory o stałym rozmiarze
  4. Interop COM i opakowania wywoływalne ze środowiska uruchomieniowego
← Powrót do C# Academy