You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 18:51:09