如何在C#与C++之间共享int等基础类型数据并解决交互异常?
C#与C++共享基础数据的实现方案
问题故障原因解释
你第二个方案抛出ExecutionEngineException确实是垃圾回收导致的:
- C#中的值类型如果是类的实例字段,会分配在托管堆中,GC执行压缩时会移动对象的内存地址,你传给C++的指针不会跟着更新,就变成野指针
- 如果值类型是方法内的局部变量,分配在栈上,方法执行结束后栈空间会被回收,C++持有的指针同样会失效
- 你直接传
ref int到非托管代码时,运行时只会在单次P/Invoke调用期间临时固定变量地址,调用返回后固定就解除了,后续C++再访问该地址必然出错
C#值类型的引用机制
- 值类型默认存储位置:方法内局部值类型分配在栈,作为引用类型的字段、装箱后的值类型分配在托管堆
ref/out是托管指针,由GC跟踪地址变动,仅在托管上下文有效,直接传给非托管代码后GC不会感知非托管侧的持有状态- 只有显式固定的内存地址,才可以安全交给非托管代码长期读写
高性能实现方案
不需要依赖Marshal.ReadInt32/Marshal.WriteInt32,通过unsafe上下文直接操作指针即可达到和普通变量几乎一致的性能:
场景1:单次调用C++期间临时共享
使用fixed关键字在调用期间临时固定变量,调用结束后自动解除固定,无内存泄漏风险:
// 首先开启项目的unsafe代码编译选项 [DllImport("你的C++库路径")] private static extern void 你的C++函数(int* sharedIntPtr); unsafe { int sharedInt = 0; // fixed块内sharedInt的地址不会被GC移动 fixed (int* p = &sharedInt) { 你的C++函数(p); // C#侧直接读写变量即可,不需要任何Marshal操作 sharedInt = 100; Console.WriteLine(*p); } }
场景2:程序生命周期内长期共享
两种可选方案,按需选择:
方案A:固定托管侧变量
int sharedInt = 0; // 装箱并固定内存,GCHandleType.Pinned标记不会被GC移动 GCHandle handle = GCHandle.Alloc(sharedInt, GCHandleType.Pinned); IntPtr sharedPtr = handle.AddrOfPinnedObject(); // 把sharedPtr传给C++作为int*使用 // C#侧读写方式(性能和普通变量一致) unsafe { int* p = (int*)sharedPtr; *p = 200; Console.WriteLine(*p); } // 程序退出前必须释放,否则会内存泄漏 handle.Free();
方案B:非托管内存分配(优化你的第一种方案)
你之前的方案性能差只是因为用了封装层厚的Marshal读写方法,直接转指针操作即可:
// 分配非托管内存,不受GC管理 IntPtr sharedPtr = Marshal.AllocHGlobal(sizeof(int)); // 把sharedPtr传给C++作为int*使用 // C#侧读写方式 unsafe { int* p = (int*)sharedPtr; *p = 300; Console.WriteLine(*p); } // 程序退出前释放内存 Marshal.FreeHGlobal(sharedPtr);
关于包装结构体的说明
如果只需要共享单个int,不需要额外包装结构体。如果后续需要共享多个关联变量,可以定义顺序布局的结构体保证C#/C++内存布局一致:
// C#侧定义 [StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack值和C++对齐设置保持一致 public struct SharedData { public int SharedInt; public float SharedFloat; public bool SharedFlag; }
// C++侧对应定义 #pragma pack(push, 4) struct SharedData { int SharedInt; float SharedFloat; bool SharedFlag; }; #pragma pack(pop)
结构体的传递、读写方式和上述单个int的操作完全一致。
内容的提问来源于stack exchange,提问作者directquest
相关产品推荐
相关产品推荐

