C++到C#持久化回调实现:需额外操作保证指针不被GC移动吗?
C#与C++互操作回调的GC安全性问题
现有实现代码
C++回调定义
namespace omk_api { enum LogSeverity : int { Error = 1, Warning = 2, Info = 3, Trace = 4 }; typedef void(__stdcall* logProc)(const wchar_t* source, const wchar_t* message, LogSeverity severity); }
C++函数导出
extern "C" __declspec(dllexport) void* __stdcall CreateController(omk_api::logProc log) { return (void*)(new omk_controller::Controller(log)); }
相关C#代码
public class Controller : IDisposable { private IntPtr instance; private LogProc logProc; [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void LogProc([MarshalAs(UnmanagedType.LPWStr)] string source, [MarshalAs(UnmanagedType.LPWStr)] string message, LogSeverity severity); // (...) [DllImport("OpenMacroKeyboard.Controller.dll")] private static extern IntPtr CreateController(LogProc log); // (...) private void Log(string source, string message, LogSeverity severity) { } // Public methods ----------------------------------------------------- public Controller() { logProc = new LogProc(Log); instance = CreateController(logProc); } // (...) }
问题
是否需要执行额外操作,以确保传递给C的指针保持不变,避免因垃圾回收器(GC)移动对象导致异常崩溃?已知C对象的生命周期与C#包装器一致,不担心垃圾回收本身的问题。
回答
你的现有实现已经足够安全,不需要额外操作,核心原因如下:
- 你通过C#类的私有成员
logProc持有了委托实例,只要Controller对象存活,这个委托就会被GC视为有效根引用,不会被回收。 - 当你把托管委托传递给非托管代码时,CLR会自动生成一个固定地址的非托管存根(thunk),C++代码实际调用的是这个存根,而非托管委托的内存地址。这个存根的地址不会被GC移动,因此不存在指针失效的风险。
补充注意点:
- 如果是在局部作用域临时创建委托并传递给C++,没有长期持有委托引用,那委托可能被GC回收导致后续调用崩溃,但你的代码里
logProc是类成员,只要Controller对象不被销毁,委托就会一直有效。 - 既然你已经保证C++对象和C#包装器生命周期一致,只要
Controller不被Dispose或回收,回调指针就始终可用。
内容的提问来源于stack exchange,提问作者Spook
相关产品推荐
相关产品推荐

