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

node-addon-api 如何原生存储事件回调供后续调用

根因说明

你当前代码无法持久化存储的问题本质有两点:

  • Napi::Env、Napi::Function都是绑定到当前函数栈的薄包装对象,函数调用结束后栈内存就会被回收,你存的对象指针是没有任何意义的野指针
  • 原生层拿到的JS函数默认是弱引用,没有做持久化标记的话,V8垃圾回收器随时会回收这个函数实例,后续调用直接触发内存错误
正确的持久化实现方案

node-addon-api提供了专门的持久化引用机制,不需要手动操作V8原始指针:

  1. 优先用模块级的静态变量存储引用,避免临时栈对象被回收
  2. 用Napi::Persistent()将传入的JS函数转成长效持有的函数引用,阻止GC回收
  3. 涉及跨线程触发的场景(比如硬件检测后台线程),必须用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.24 07:36:03