node-addon-api 如何原生存储事件回调供后续调用
根因说明
你当前代码无法持久化存储的问题本质有两点:
Napi::Env、Napi::Function都是绑定到当前函数栈的薄包装对象,函数调用结束后栈内存就会被回收,你存的对象指针是没有任何意义的野指针- 原生层拿到的JS函数默认是弱引用,没有做持久化标记的话,V8垃圾回收器随时会回收这个函数实例,后续调用直接触发内存错误
正确的持久化实现方案
node-addon-api提供了专门的持久化引用机制,不需要手动操作V8原始指针:
- 优先用模块级的静态变量存储引用,避免临时栈对象被回收
- 用
Napi::Persistent()将传入的JS函数转成长效持有的函数引用,阻止GC回收 - 涉及跨线程触发的场景(比如硬件检测后台线程),必须用
Napi::ThreadSafeFunction做线程安全封装,不能直接在非主线程调用JS函数
对应代码改造
1. 纯主线程场景(不需要后台线程触发的简单场景)
// 模块级静态存储 static Napi::FunctionReference g_persistEmitRef; static Napi::Env g_env; Napi::Boolean Node_CardList_AddCardStatusNotifier(const Napi::CallbackInfo& info){ Napi::Env env = info.Env(); Napi::Function emitFn = info[0].As<Napi::Function>(); // 将临时函数对象转成持久化引用,避免被GC回收 g_persistEmitRef = Napi::Persistent(emitFn); // 存储Env实例(不是指针) g_env = env; return Napi::Boolean::New(env, true); } // 任意主线程时机触发回调的写法 void TriggerCardInsertEvent() { g_persistEmitRef.Call({ Napi::String::New(g_env, "onCardInserted") }); }
2. 跨线程场景(硬件检测等后台线程触发)
如果你的事件触发源不在Node主线程(比如串口监听线程、硬件中断回调线程),必须用线程安全函数切回主线程操作JS对象,否则会直接崩溃:
#include <thread> // 模块级静态存储线程安全函数 static Napi::ThreadSafeFunction g_tsfn; static std::thread g_detectThread; Napi::Boolean Node_CardList_AddCardStatusNotifier(const Napi::CallbackInfo& info){ Napi::Env env = info.Env(); Napi::Function emitFn = info[0].As<Napi::Function>(); // 创建带持久化能力的线程安全函数 g_tsfn = Napi::ThreadSafeFunction::New( env, emitFn, "CardStatusNotifier", 0, // 消息队列长度,0代表无限制 1 ); // 启动后台检测线程 g_detectThread = std::thread([](){ while(true) { // 模拟后台检测到硬件状态变化 bool isCardIn = /* 读取硬件状态 */; // 把回调投递回JS主线程执行 g_tsfn.BlockingCall([isCardIn](Napi::Env env, Napi::Function emitFn) { const std::string eventName = isCardIn ? "onCardInserted" : "onCardRemoved"; emitFn.Call({Napi::String::New(env, eventName)}); }); // 检测间隔 std::this_thread::sleep_for(std::chrono::milliseconds(500)); } }); return Napi::Boolean::New(env, true); } // 插件卸载时释放资源 void OnPluginUnload(Napi::Env env) { g_tsfn.Release(); if(g_detectThread.joinable()) { g_detectThread.join(); } }
常见踩坑点
- 绝对不要取
&env、&emit这种栈对象的指针,函数退出后栈内存就会被复用,存的指针完全失效 - 不要直接把临时的
Napi::Function对象存在全局变量里,没有经过Napi::Persistent()封装的引用随时会被GC回收 - 非主线程绝对不能直接调用JS函数,必须通过
ThreadSafeFunction把执行逻辑投递回Node主线程 - 插件销毁、不再需要回调的时候,一定要调用
Release方法释放线程安全函数,避免内存泄漏
内容的提问来源于stack exchange,提问作者Martin
相关产品推荐
相关产品推荐

