Search code examples
javascriptnode.jsv8n-apinode-api

Store function reference to call later from native module in N-API


I have a simple native Node.js addon that uses N-API.

It exports two functions, set_callback and call_callback:

  • set_callback takes a function parameter and assigns it to a global variable js_callback
  • call_callback calls the js_callback using napi_call_function()

I'm using the addon like this:

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

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

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

Inside the set_callback function, js_callback actually points to the passed function and it is usable:

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

However, in call_callback this same global variable now refers to something else: it points to the same value, but now has a different type:

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

Which causes napi_call_function() to fail:

addon.call_callback();
      ^

Error: Invalid argument
    at Object.<anonymous>

What is the proper way to store a function reference to be able to call it later in N-API?

Source code for the addon:

#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)

Solution

  • I'm not a N-API expert, but in V8 terms, what you need is a v8::Persistent. From a quick look at the N-API documentation, it seems that's called a "reference" there.