C#之.Net互操作-平台调用(P_Invoke)
2026/6/25 14:25:53 网站建设 项目流程

平台调用(Platform Invoke,简称 P/Invoke)是 .NET 公共语言运行时(CLR)提供的一种互操作功能,允许托管代码调用从非托管动态链接库(DLL,如 Win32 API 或自定义 C++ DLL)中导出的静态函数。

在托管代码调用非托管函数时,CLR 的封送拆收器(Interop Marshaler)扮演着桥梁角色:

  • 拦截调用:当托管调用触发时,封送拆收器拦截请求。
  • 内存转换(封送 IN):将托管堆/栈中的数据转换为非托管端能够理解的物理布局(如将托管string转换为非托管char*)。
  • 切换线程上下文:从托管执行环境切换到非托管执行环境,执行原生的机器码。
  • 结果回写(封送 OUT):非托管函数返回后,将结果或修改后的数据传回托管端,并释放临时缓冲区。

平台调用数据类型映射表

要构造正确的托管原型,必须使用大小、对齐方式完全对等的托管类型替换 Win32/C 样式的数据类型:

Wtypes.h 中
非托管类型
非托管
C 语言类型
托管类型说明
HANDLEvoid*System.IntPtr32 位系统上为 32 位,64 位系统上为 64 位
BYTEunsigned charSystem.Byte8 位无符号整数
SHORTshortSystem.Int1616 位有符号整数
WORDunsigned shortSystem.UInt1616 位无符号整数
INT / LONGint / longSystem.Int3232 位有符号整数
UINT / DWORD / ULONGunsigned int / longSystem.UInt3232 位无符号整数
BOOLlongSystem.Int3232 位布尔值标志。托管端需用[return: MarshalAs(UnmanagedType.Bool)]保证 4 字节映射
CHARcharSystem.Char用 ANSI 修饰,映射到托管端时需注意字符集转换
LPSTR / LPCSTRchar* / const char*System.String
/StringBuilder
ANSI 字符串指针(LPCSTR为只读)
LPWSTR / LPCWSTRwchar_t* / const _System.String
/StringBuilder
Unicode 字符串指针(LPCWSTR为只读)
FLOATfloatSystem.Single32 位单精度浮点数
DOUBLEdoubleSystem.Double64 位双精度浮点数

关键内存与字符串封送

不可变性与 StringBuilder

托管System.String在 CLR 中是绝对不可变的。当非托管函数需要修改字符串并将其作为出参(In/Out 缓冲区)传回时,坚决不能使用string。否则,封送拆收器会修改其内部临时的只读缓冲区,轻则导致数据丢失,重则破坏托管堆(Heap Corruption),引发不可预知的崩溃。

  • 黄金法则:凡是非托管端需要写入、修改的字符缓冲区,托管端必须使用System.Text.StringBuilder显式预先实例化并分配足够的 Capacity。

内存所有权闭环原则

当托管代码与非托管代码在堆上动态交换内存(如非托管代码内部动态分配了一个结构体或字符串数组,并返回二级指针给 C#)时,双方必须在内存分配器(Allocator)上达成绝对一致。

  • 默认标准:CLR 的 P/Invoke 默认使用 Win32 堆的 COM 内存分配器(即组件对象模型的CoTaskMemAlloc)。
  • 行为闭环:如果 C++ 导出函数需要向托管端返回动态生成的字符串或修改过的指针,C++ 内部必须使用CoTaskMemAlloc申请内存,而不是原生newmalloc。只有这样,托管端的封送拆收器在完成拆收后,才能正确调用Marshal.FreeCoTaskMem(或由开发人员手动调用)来闭环释放内存,杜绝内存泄漏。

基础场景:枚举、常量与指针

枚举与常量封装

以 Win32 的MessageBeep为例,非托管原型为:

BOOLMessageBeep(UINT uType);

虽然uType被定义为UINT,但 MSDN 文档指出其支持-1(标准提示音)。在 C# 中为了保证代码自解释性与健壮性,应将其封装为枚举:

publicenumBeepType:int{SimpleBeep=-1,IconAsterisk=0x00000040,IconExclamation=0x00000030,IconHand=0x00000010,IconQuestion=0x00000020,Ok=0x00000000,}publicstaticclassWin32Native{[DllImport("user32.dll",SetLastError=true)][return:MarshalAs(UnmanagedType.Bool)]// 确保将 4 字节 Win32 BOOL 正确转换为托管 boolpublicstaticexternboolMessageBeep(BeepTypebeepType);}

非 opaque 指针(句柄)的通用映射

Win32 API 中存在大量不透明(Opaque)指针,最典型的代表就是句柄(如HANDLE,HWND)。托管代码不需要知道指针指向的内部结构,只需要存储并原样传回给操作系统。

  • 直接使用System.IntPtr进行通用映射。不要附加refout修饰符,因为IntPtr自身的大小在运行时会自动适配操作系统位数(32位系统占4字节,64位系统占8字节)。

复杂数据结构封送

普通结构体与内存对齐

如果非托管函数接受结构体指针,在托管端可以通过定义相同的struct配合ref关键字实现双向封送。

非托管 C++ 结构体:

typedefstruct_SYSTEM_POWER_STATUS{BYTE ACLineStatus;BYTE BatteryFlag;BYTE BatteryLifePercent;BYTE Reserved1;DWORD BatteryLifeTime;DWORD BatteryFullLifeTime;}SYSTEM_POWER_STATUS;

托管 C# 对应实现:

[StructLayout(LayoutKind.Sequential)]// 必须保证字段在内存中严格按顺序排列publicstructSystemPowerStatus{public

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询