平台调用的源生成

.NET 7 为 P/Invoke 引入了一个源生成器,用于识别 C# 代码中的 LibraryImportAttribute

当 P/Invoke 不使用源生成时,.NET 运行时中的内置互操作系统将生成一个 IL 存根,即在运行时以 JIT 为特征的 IL 指令流,以便于从托管到非托管的转换。 以下代码演示如何定义并随后调用使用此机制的 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 应用程序模型通过将所有代码提前直接预编译为本机代码来解决动态代码生成问题。 对于需要完整本机 AOT 方案的平台,不能选择使用 DllImport 选项,因此,使用其他方法(例如源生成)更合适。 在 DllImport 方案中,调试封送逻辑也是一项重要的练习。

P/Invoke 源生成器(随 .NET 7 SDK 一起提供并默认启用)在 staticpartial 方法上查找 LibraryImportAttribute 以触发封送代码的编译时源生成,从而无需在运行时生成 IL 存根,并允许内联 P/Invoke。 此外,还包括分析器和代码修复程序,以帮助从内置系统迁移到源生成器以及一般使用情况。

基本用法

LibraryImportAttribute 在用法上与 DllImportAttribute 类似。 我们可以通过使用 LibraryImportAttribute 并将方法标记为 partial 而不是 extern 将前述示例转换为使用 P/Invoke 源生成:

[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 直接转换,但会特意进行一些更改:

MarshalAsAttribute 上的某些设置、某些类型的默认封送以及其他与互操作相关的属性的支持也存在差异。 有关更多信息,请参阅有关兼容性差异的文档

请参阅