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 bodySetLastError и обработка ошибок
Установите 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 — локальная установка не требуется.
Все уроки этого курса
- Основы P/Invoke
- LibraryImport и P/Invoke из исходного кода
- Небезопасный код, указатели и буферы фиксированного размера
- Взаимодействие с COM и оболочки, вызываемые средой выполнения