Создание источника для вызовов платформы
.NET 7 представляет генератор источника для P/Invokes, который распознает LibraryImportAttribute код C#.
Если он не использует создание источника, встроенная система взаимодействия в среде выполнения .NET создает заглушку IL - поток инструкций IL, который является JIT-ed, во время выполнения, чтобы упростить переход с управляемого на неуправляемый. В следующем коде показано определение и вызов P/Invoke, который использует этот механизм:
[DllImport(
"nativelib",
EntryPoint = "to_lower",
CharSet = CharSet.Unicode)]
internal static extern string ToLower(string str);
// string lower = ToLower("StringToConvert");
Заглушка IL обрабатывает маршалирование параметров и возвращаемых значений и вызывает неуправляемый код при соблюдении параметров, DllImportAttribute влияющих на вызов неуправляемого кода (например, SetLastError). Так как этот заглушок IL создается во время выполнения, он недоступен для предварительного компилятора (AOT) или сценариев обрезки IL. Создание IL представляет важную стоимость для маршаллинга. Эта стоимость может измеряться с точки зрения производительности приложения и поддержки потенциальных целевых платформ, которые могут не разрешать динамическое создание кода. Модель приложения AOT в машинном коде решает проблемы с динамическим созданием кода путем предварительной компиляции всего кода непосредственно в машинный код. Использование DllImport
не является вариантом для платформ, для которых требуются полные сценарии AOT в собственном коде, поэтому использование других подходов (например, создание источника) является более подходящим. Отладка логики маршаллинга в DllImport
сценариях также является нетривиальным упражнением.
Генератор исходного кода P/Invoke, включенный в пакет SDK для .NET 7 и включенный по умолчанию, ищет LibraryImportAttribute static
partial
и метод для активации создания исходного кода маршалинга во время компиляции, удаления необходимости создания заглушки IL во время выполнения и разрешения встраивание P/Invoke. Анализаторы и средства исправления кода также включаются для миграции из встроенной системы в генератор источника и общего использования.
Базовое использование
Он LibraryImportAttribute предназначен для того, чтобы быть похожим на DllImportAttribute использование. Мы можем преобразовать предыдущий пример, чтобы использовать создание источника P/Invoke, используя LibraryImportAttribute метод и помечая его как partial
не extern
так:
[LibraryImport(
"nativelib",
EntryPoint = "to_lower",
StringMarshalling = StringMarshalling.Utf16)]
internal static partial string ToLower(string str);
Во время компиляции генератор источника активирует для создания реализации ToLower
метода, обрабатывающего маршаллирование string
параметра и возвращаемое значение как UTF-16. Так как маршалирование теперь создается исходный код, вы можете на самом деле просмотреть и выполнить шаги по логике в отладчике.
MarshalAs
Генератор источника также учитывает MarshalAsAttribute. Предыдущий код также может быть написан следующим образом:
[LibraryImport(
"nativelib",
EntryPoint = "to_lower")]
[return: MarshalAs(UnmanagedType.LPWStr)]
internal static partial string ToLower(
[MarshalAs(UnmanagedType.LPWStr)] string str);
Некоторые параметры для MarshalAsAttribute них не поддерживаются. Генератор источника выдает ошибку, если вы пытаетесь использовать неподдерживаемые параметры. Дополнительные сведения см. в разделе "Отличия от DllImport".
Соглашение о вызовах
Чтобы указать соглашение о вызовах, используйте UnmanagedCallConvAttribute, например:
[LibraryImport(
"nativelib",
EntryPoint = "to_lower",
StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(
CallConvs = new[] { typeof(CallConvStdcall) })]
internal static partial string ToLower(string str);
Различия от DllImport
LibraryImportAttribute Предназначено для простого преобразования из DllImportAttribute большинства случаев, но есть некоторые преднамеренные изменения:
- CallingConvention не имеет эквивалентного для LibraryImportAttribute. UnmanagedCallConvAttribute вместо этого следует использовать.
- CharSet (для CharSet) заменено StringMarshalling на (for StringMarshalling). ANSI удален, а UTF-8 теперь доступен в качестве первого класса.
- BestFitMapping и ThrowOnUnmappableChar не имеет эквивалента. Эти поля были актуальными только при маршаллинге строки ANSI в Windows. Созданный код для маршалинга строки ANSI будет иметь эквивалентное поведение
BestFitMapping=false
иThrowOnUnmappableChar=false
. - ExactSpelling не имеет эквивалента. Это поле было ориентировано на Windows и не повлияло на операционные системы, отличные от Windows. Имя метода или EntryPoint должно быть точным орфографией имени точки входа. Это поле использует исторические методы, связанные с
A
суффиксами,W
используемыми в программировании Win32. - PreserveSig не имеет эквивалента. Это поле было параметром, ориентированным на Windows. Созданный код всегда преобразует подпись напрямую.
- Проект должен быть помечен как небезопасный с помощью AllowUnsafeBlocks.
Существуют также различия в поддержке некоторых MarshalAsAttributeпараметров по умолчанию для маршаллинга определенных типов и других атрибутов взаимодействия. Дополнительные сведения см. в документации по различиям совместимости.