当前位置: 首页 > news >正文

C#之.Net互操作-平台调用(P_Invoke)

平台调用(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
http://www.jsqmd.com/news/1075324/

相关文章:

  • FanControl完整指南:如何免费掌控Windows电脑风扇,告别噪音烦恼
  • 你AI的 localhost:3000,可以立刻在网上访问了!
  • 小红书、抖音、支付宝都能碰一碰分享,鸿蒙7的社交新玩法
  • 波普尔病毒:人工智能大模型的系统性认知癌症——论证伪主义在AI系统中的程序化扩散与文明危害
  • Sherlock.js:让自然语言变身日程助手,3分钟解锁智能事件解析
  • DDD-030:DDD 落地常见问题与避坑指南
  • 【C语言】c语言基础知识梳理(超全)
  • LSTM股票收益率预测实战:从数据清洗到模型部署
  • TVA在物流分拣领域的独特价值(7)
  • 用数据说话!2026年最流行AI论文软件榜单,免费款也能高效产初稿
  • Canonical Livepatch 正式拥抱 Arm64:ARM 服务器终于也能零停机打内核补丁了
  • 从AI用户到建造者:2025年可落地的AI系统工程实践指南
  • Poly Haven Assets:Blender中获取免费3D资源库的终极指南
  • Geoserver表达式注入漏洞CVE-2024-36401:从原理到复现与防御
  • 终极Windows老游戏兼容解决方案:5分钟让经典游戏在Win10/11上完美运行
  • Sketch Measure:告别设计标注痛苦,让协作变得轻松有趣
  • 可以边录边编辑的音乐平台,多款录音修音一体化工具实操分享
  • 网站建设如何适配本地流量?GEO 推广导向型建站逻辑详解
  • AI录音后期处理软件:录歌、修音、剪辑导出一体化工具梳理
  • 为什么创作者要关心 CLI
  • EXTI+定时器消抖:按键中断的正确姿势
  • 终极指南:如何用Blender插件实现Unreal PSK/PSA文件导入导出
  • 三步掌握BilibiliDown:你的B站视频离线宝库
  • Ryujinx:终极Nintendo Switch模拟器完整指南与快速上手教程
  • Hadoop 序列化与 HDFS 连接:从入门到踩坑全记录
  • RLAIF实战指南:用AI反馈替代人工标注的三大技术路径
  • 先引入一个简单的例子,给定一个 4 个节点 4 条边的有向带权图:
  • 临床数据难转化成果?打通临床统计与医工协同的数字化方案
  • ERNIE-Image-Turbo轻量化图像生成与OpenMementos记忆压缩实战
  • WebKitGTK架构下的高效浏览器实现:Midori的模块化设计与性能优化