LibraryImport 与源代码生成的 P/Invoke
使用 [LibraryImport](C# 11+)实现兼容 AOT 的源代码生成封送处理,其性能优于 DllImport。
LibraryImport 与源代码生成的 P/Invoke 是 CoddyKit 上的免费 C# Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
为什么使用 LibraryImport
[LibraryImport] 在 .NET 7(C# 11)中引入,作为由源代码生成且兼容 AOT 的 [DllImport] 替代方案。传统的 DllImport 依赖通过反射在运行时进行封送,这对 Native AOT 来说存在问题。LibraryImport 会在编译时生成全部封送代码。
声明 LibraryImport 方法
请使用 [LibraryImport] 标记一个 static partial 方法。源代码生成器会补全其实现。同时,包含该方法的类也必须标记为 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 要求 you 显式指定字符串封送方式。您可以使用 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 与错误处理
在 [LibraryImport] 中设置 SetLastError = true,以捕获操作系统错误代码。调用结束后,请使用 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 声明保持为内部或私有成员,只向外部公开安全 wrappers。
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;
}快速检查
与 [DllImport] 相比,[LibraryImport] 的主要优势是什么?
回顾:LibraryImport 与源代码生成的 P/Invoke
要点:
[LibraryImport]替代[DllImport],用于支持 AOT 的 P/Invoke- 封送在编译时生成,无需运行时反射
- 需要
static partial方法和partial类 - 必须通过
StringMarshalling或MarshalAs显式指定字符串封送方式 - 提供一等
Span<T>支持,无需承担固定带来的开销 - 使用
EmitCompilerGeneratedFiles检查生成的代码
常见问题解答
「LibraryImport 与源代码生成的 P/Invoke」课时是免费的吗?
是的 — 「LibraryImport 与源代码生成的 P/Invoke」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「LibraryImport 与源代码生成的 P/Invoke」这节课中我会学到什么?
使用 [LibraryImport](C# 11+)实现兼容 AOT 的源代码生成封送处理,其性能优于 DllImport。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「LibraryImport 与源代码生成的 P/Invoke」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- P/Invoke 基础
- LibraryImport 与源代码生成的 P/Invoke
- 不安全代码、指针与固定缓冲区
- COM 互操作与运行时可调用包装器