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

如何在N-API中存储函数引用以便后续调用?

N-API中存储函数引用以便后续调用的正确方式

问题描述

我有一个使用N-API的简单Node.js原生插件,导出两个函数:

  • set_callback:接收一个函数参数并赋值给全局变量js_callback
  • call_callback:使用napi_call_function()调用js_callback

插件使用代码

const addon = require("./build/Release/addon.node");

const callback = function() {
    console.log("The callback was called!");
};

addon.set_callback(callback);
addon.call_callback();

异常现象

在set_callback内部,js_callback是正确的函数类型:

set_callback:
  js_callback = 00000053268FEE88
  typeof(js_callback) = 7 (napi_function)

但在call_callback中,同一个全局变量地址不变,但类型变为napi_object:

call_callback:
  js_callback = 00000053268FEE88
  typeof(js_callback) = 6 (napi_object)

最终导致调用失败:

addon.call_callback();
      ^

Error: Invalid argument
    at Object.<anonymous>

插件源代码

#include <stdio.h>
#include <node_api.h>

napi_value js_null;
napi_value js_callback;

napi_value addon_set_callback(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1];

    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));

    js_callback = argv[0];

    printf("set_callback:\n");

    napi_valuetype type;
    NAPI_CALL(env, napi_typeof(env, js_callback, &type));

    printf("  js_callback = %p\n", js_callback);
    printf("  typeof(js_callback) = %d\n\n", type);
    return js_null;
}

napi_value addon_call_callback(napi_env env, napi_callback_info info) {
    printf("call_callback:\n");

    napi_valuetype type;
    NAPI_CALL(env, napi_typeof(env, js_callback, &type));

    printf("  js_callback = %p\n", js_callback);
    printf("  typeof(js_callback) = %d\n\n", type);

    napi_value result;
    NAPI_CALL(env, napi_call_function(env, js_null, js_callback, 0, NULL, &result));

    return js_null;
}

napi_value addon_init(napi_env env, napi_value exports) {
    napi_get_null(env, &js_null);

    napi_value fn_set_callback;
    napi_value fn_call_callback;

    NAPI_CALL(env, napi_create_function(env, NULL, 0, addon_set_callback, NULL, &fn_set_callback));
    NAPI_CALL(env, napi_create_function(env, NULL, 0, addon_call_callback, NULL, &fn_call_callback));

    NAPI_CALL(env, napi_set_named_property(env, exports, "set_callback", fn_set_callback));
    NAPI_CALL(env, napi_set_named_property(env, exports, "call_callback", fn_call_callback));

    return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, addon_init)

解决方案

问题根源在于直接将napi_value存储为全局变量——napi_value只是当前N-API环境下的临时引用,无法保证在后续调用中始终有效,Node.js的垃圾回收机制可能会回收该值,或者其内部状态发生变化。

正确的做法是使用N-API的引用管理机制,通过napi_create_reference创建持久引用,napi_delete_reference释放引用,调用时通过napi_get_reference_value获取当前有效的napi_value。

修改后的插件代码

#include <stdio.h>
#include <node_api.h>

napi_value js_null;
// 将全局变量改为存储持久引用,而非直接存储napi_value
napi_ref js_callback_ref = NULL;

napi_value addon_set_callback(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1];

    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));

    // 释放之前的引用,避免内存泄漏
    if (js_callback_ref != NULL) {
        napi_delete_reference(env, js_callback_ref);
        js_callback_ref = NULL;
    }

    // 创建持久引用,第三个参数为引用计数(设为1防止被GC回收)
    NAPI_CALL(env, napi_create_reference(env, argv[0], 1, &js_callback_ref));

    printf("set_callback:\n");

    napi_valuetype type;
    NAPI_CALL(env, napi_typeof(env, argv[0], &type));

    printf("  callback value = %p\n", argv[0]);
    printf("  typeof(callback) = %d\n\n", type);
    return js_null;
}

napi_value addon_call_callback(napi_env env, napi_callback_info info) {
    if (js_callback_ref == NULL) {
        printf("call_callback: No callback set\n");
        return js_null;
    }

    napi_value js_callback;
    // 通过持久引用获取当前有效的napi_value
    NAPI_CALL(env, napi_get_reference_value(env, js_callback_ref, &js_callback));

    printf("call_callback:\n");

    napi_valuetype type;
    NAPI_CALL(env, napi_typeof(env, js_callback, &type));

    printf("  callback value = %p\n", js_callback);
    printf("  typeof(callback) = %d\n\n", type);

    napi_value result;
    NAPI_CALL(env, napi_call_function(env, js_null, js_callback, 0, NULL, &result));

    return js_null;
}

napi_value addon_init(napi_env env, napi_value exports) {
    napi_get_null(env, &js_null);

    napi_value fn_set_callback;
    napi_value fn_call_callback;

    NAPI_CALL(env, napi_create_function(env, NULL, 0, addon_set_callback, NULL, &fn_set_callback));
    NAPI_CALL(env, napi_create_function(env, NULL, 0, addon_call_callback, NULL, &fn_call_callback));

    NAPI_CALL(env, napi_set_named_property(env, exports, "set_callback", fn_set_callback));
    NAPI_CALL(env, napi_set_named_property(env, exports, "call_callback", fn_call_callback));

    return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, addon_init)

关键修改点

  • 全局变量从napi_value js_callback改为napi_ref js_callback_ref,用于存储函数的持久引用
  • 在set_callback中,创建新引用前先释放旧引用,避免内存泄漏;调用napi_create_reference生成持久引用
  • 在call_callback中,通过napi_get_reference_value从持久引用中获取当前有效的函数值,再执行调用

这样就能确保函数引用在后续调用中始终保持有效,不会出现类型变更或调用失败的问题。


内容的提问来源于stack exchange,提问作者user7401478

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 02:30:51