如何通过Node-Addon-API从C++调用JS回调并避免多上下文覆盖?
解决多上下文C库调用JS回调被覆盖的问题
当前代码的核心问题是用全局变量存储JS回调,导致多个上下文(ctx1、ctx2)的回调互相覆盖。要让每个上下文的回调独立触发,需要把每个上下文对应的回调数据和C库句柄绑定,具体修改如下:
核心思路
不再使用全局变量存储回调,而是为每个上下文创建独立的回调数据结构体,将其作为userdata传递给C库的回调函数。这样C库触发事件时,能通过userdata找到当前上下文对应的JS回调。
修改后的代码实现
1. 定义回调数据结构体
封装每个上下文的N-API环境和持久化回调:
// 存储每个上下文的回调相关数据 struct CallbackData { Napi::FunctionReference callback; Napi::Env env; // 若C库在多线程触发回调,需添加ThreadSafeFunction: // Napi::ThreadSafeFunction tsfn; };
2. 修改C库事件回调函数
从userdata中取出当前上下文的回调数据,调用对应的JS回调:
extern "C" void onEvent(mylib_t* handle, void* userdata) { CallbackData* data = static_cast<CallbackData*>(userdata); if (data != nullptr && !data->callback.IsEmpty()) { // 注意:多线程场景必须用ThreadSafeFunction调用,以下是单线程简化示例 data->callback.Call({}); } }
3. 更新SetEventCallback方法
为每个上下文创建独立的回调数据,传递给C库并绑定内存清理逻辑:
Napi::Value MyWrapper::SetEventCallback(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); mylib_t* handle = convertJSBigIntToHandle(info[0]); Napi::Function jsCallback = info[1].As<Napi::Function>(); // 分配内存存储当前上下文的回调数据 CallbackData* data = new CallbackData(); data->env = env; data->callback = Napi::Persistent(jsCallback); // 可选:设置弱引用避免内存泄漏,需根据业务场景调整 // data->callback.SetWeak(); // 将回调数据作为userdata传给C库 const auto ret = mylib_set_callback(handle, onEvent, data); // 绑定内存清理逻辑,上下文销毁时释放资源 env.AddCleanupHook([](void* cleanupData) { CallbackData* cbData = static_cast<CallbackData*>(cleanupData); cbData->callback.Reset(); delete cbData; }, data); return env.Null(); }
关键注意事项
- 多线程安全:如果C库在非V8主线程触发事件,必须使用
Napi::ThreadSafeFunction来调用JS回调,直接调用Call()会导致崩溃。需将结构体中的FunctionReference替换为ThreadSafeFunction,并通过BlockingCall或NonBlockingCall触发回调。 - 内存管理:务必在上下文销毁或用户移除回调时释放
CallbackData内存,避免内存泄漏。示例中通过AddCleanupHook监听环境销毁事件来自动清理。 - 避免全局状态:每个上下文的回调数据独立存储,通过
userdata与C库句柄绑定,彻底解决多上下文回调覆盖问题。
内容的提问来源于stack exchange,提问作者Alex Shaw
相关产品推荐
相关产品推荐

