0Pricing
C# Academy · Урок

LibraryImport и P/Invoke из исходного кода

Используйте [LibraryImport] (C# 11+) для совместимого с AOT маршалинга, генерируемого из исходного кода и превосходящего DllImport по производительности.

«LibraryImport и P/Invoke из исходного кода» — бесплатный урок C# Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Зачем нужен LibraryImport

[LibraryImport] появился в .NET 7 (C# 11) как совместимая с AOT замена [DllImport], использующая генерацию исходного кода. Классический DllImport полагается на маршалинг во время выполнения через отражение — это проблематично для Native AOT. LibraryImport генерирует весь код маршалинга во время компиляции.

Объявление метода LibraryImport

Пометьте статический метод static partial атрибутом [LibraryImport]. Генератор исходного кода заполнит реализацию. Также необходимо пометить содержащий его класс как 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);
}

Маршалинг строк в LibraryImport

В отличие от DllImport, LibraryImport требует явно указать маршалинг строк. Можно использовать перечисление StringMarshalling или атрибуты MarshalAs, поэтому стоимость маршалинга становится видимой и управляемой.

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

Маршалинг структур

Для структур добавьте [NativeMarshalling], чтобы определить, как управляемый тип отображается на его нативное представление. Генератор исходного кода использует тип маршалера для создания безопасного кода, сводящего к минимуму выделение памяти.

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

Сравнение DllImport и LibraryImport

DllImport интерпретируется во время выполнения — медленный запуск, зависимость от отражения и несовместимость с удалением кода при AOT. LibraryImport генерирует оптимизированный код C# во время сборки: без отражения во время выполнения, с безопасным удалением кода и измеримо более высокой скоростью в тестах производительности.

// 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 и обработка ошибок

Установите SetLastError = true в [LibraryImport], чтобы сохранить код ошибки ОС. Используйте Marshal.GetLastPInvokeError() (предпочтительно) или Marshal.GetLastWin32Error(), чтобы получить его после вызова.

[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> и маршалинг памяти

Одно из преимуществ P/Invoke с генерацией исходного кода — полноценная поддержка Span<T>. Передача ReadOnlySpan<byte> позволяет избежать закрепления и выделения памяти, которые потребовались бы при использовании массивов с 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);

Включение генерации исходного кода

Генерация исходного кода включается автоматически, когда в проекте, предназначенном для .NET 7 и более новых версий, вы ссылаетесь на пространство имён System.Runtime.InteropServices. Для более старых целевых платформ требуется пакет анализатора 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>

Просмотр сгенерированного кода

Добавьте <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> в файл csproj, чтобы записывать сгенерированные файлы в obj/. Это позволяет точно увидеть, что создаёт генератор исходного кода, — отличный способ закрепить материал на практике.

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

Практический пример: обёртка над библиотекой C

Распространённый подход — определить статический класс-обёртку со всеми объявлениями LibraryImport, а затем предоставить поверх него высокоуровневый безопасный API. Оставляйте объявления partial внутренними или закрытыми, а публично предоставляйте только безопасные обёртки.

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

Быстрая проверка

В чём главное преимущество [LibraryImport] перед [DllImport]?

Повторение: LibraryImport и P/Invoke с генерацией исходного кода

Основные выводы:

  • [LibraryImport] заменяет [DllImport] для безопасного при AOT P/Invoke
  • Маршалинг генерируется во время компиляции — отражение во время выполнения не требуется
  • Требуются метод static partial и класс partial
  • Маршалинг строк необходимо явно задавать через StringMarshalling или MarshalAs
  • Полноценная поддержка Span<T> без затрат на закрепление памяти
  • Просматривайте сгенерированный код с помощью EmitCompilerGeneratedFiles

Часто задаваемые вопросы

Урок «LibraryImport и P/Invoke из исходного кода» бесплатный?

Да — полный текст урока «LibraryImport и P/Invoke из исходного кода» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «LibraryImport и P/Invoke из исходного кода»?

Используйте [LibraryImport] (C# 11+) для совместимого с AOT маршалинга, генерируемого из исходного кода и превосходящего DllImport по производительности. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «LibraryImport и P/Invoke из исходного кода»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Основы P/Invoke
  2. LibraryImport и P/Invoke из исходного кода
  3. Небезопасный код, указатели и буферы фиксированного размера
  4. Взаимодействие с COM и оболочки, вызываемые средой выполнения
← Назад к C# Academy