Created
June 12, 2018 18:17
-
-
Save gabrielschulhof/d24c1f66b9f48896318e332d8a756b87 to your computer and use it in GitHub Desktop.
N-API multi-context addon example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdlib.h> | |
#include <node_api.h> | |
// As a convenience, wrap N-API calls such that they cause Node.js to abort | |
// when they are unsuccessful. | |
#define NAPI_CALL(call) \ | |
do { \ | |
napi_status status = call; \ | |
if (status != napi_ok) { \ | |
napi_fatal_error(#call, NAPI_AUTO_LENGTH, "failed", NAPI_AUTO_LENGTH); \ | |
} \ | |
} while (0) | |
// We store addon state in a structure instead of defining global static | |
// variables. | |
typedef struct { | |
napi_ref callback_function; | |
size_t call_count; | |
} AddonState; | |
// Node.js will call this function when the addon unloads, giving us a chance to | |
// free our "global static" variables and the structure holding them. This is | |
// currently not yet implemented in Node.js. | |
static void DestroyState(napi_env env, void* data, void* hint) { | |
AddonState* state = data; | |
// If we have a JavaScript callback, we call it with the special value "free" | |
// before destroying the reference we're holding. This illustrates the fact | |
// that we still have access to JavaScript even as the addon is unloaded. | |
if (state->callback_function != NULL) { | |
napi_value js_cb, string, undefined; | |
NAPI_CALL(napi_get_reference_value(env, state->callback_function, &js_cb)); | |
NAPI_CALL(napi_create_string_utf8(env, "free", NAPI_AUTO_LENGTH, &string)); | |
NAPI_CALL(napi_get_undefined(env, &undefined)); | |
NAPI_CALL(napi_call_function(env, undefined, js_cb, 1, &string, NULL)); | |
NAPI_CALL(napi_delete_reference(env, state->callback_function)); | |
} | |
free(state); | |
} | |
// Call the previously specified callback, if any, and increment the call count. | |
static napi_value CallCallback(napi_env env, napi_callback_info info) { | |
size_t argc = 0; | |
void* data; | |
AddonState* state; | |
// Retrieve the addon state. This binding takes no parameters. | |
NAPI_CALL(napi_get_cb_info(env, info, &argc, NULL, NULL, &data)); | |
state = data; | |
// If we have a reference to a JavaScript callback then retrieve it from our | |
// reference, call it, and increment the call count. | |
if (state->callback_function) { | |
napi_value js_cb, undefined; | |
NAPI_CALL(napi_get_reference_value(env, state->callback_function, &js_cb)); | |
NAPI_CALL(napi_get_undefined(env, &undefined)); | |
NAPI_CALL(napi_call_function(env, undefined, js_cb, 0, NULL, NULL)); | |
state->call_count++; | |
} | |
// Return to JavaScript the current call count. | |
napi_value return_value; | |
NAPI_CALL(napi_create_uint32(env, state->call_count, &return_value)); | |
return return_value; | |
} | |
// Specify which JavaScript callback to call henceforth. | |
static napi_value SetCallback(napi_env env, napi_callback_info info) { | |
size_t argc = 1; | |
napi_value js_cb; | |
napi_valuetype js_cb_type; | |
void* data; | |
AddonState* state; | |
// Retrieve the addon state and the JavaScript callback. | |
NAPI_CALL(napi_get_cb_info(env, info, &argc, &js_cb, NULL, &data)); | |
state = data; | |
// Delete our reference to any existing JavaScript callback and reset the call | |
// count. | |
if (state->callback_function != NULL) { | |
NAPI_CALL(napi_delete_reference(env, state->callback_function)); | |
state->callback_function = NULL; | |
state->call_count = 0; | |
} | |
// Set a new callback if js_cb is a JavaScript function, do nothing if it is | |
// null or undefined, and throw a fatal error if it is anything else. | |
NAPI_CALL(napi_typeof(env, js_cb, &js_cb_type)); | |
if (js_cb_type == napi_function) { | |
NAPI_CALL(napi_create_reference(env, js_cb, 1, &state->callback_function)); | |
} else if (!(js_cb_type == napi_null || js_cb_type == napi_undefined)) { | |
napi_fatal_error("SetCallback", NAPI_AUTO_LENGTH, | |
"Callback is neither null, nor undefined, nor a function", | |
NAPI_AUTO_LENGTH); | |
} | |
return NULL; | |
} | |
// Using NAPI_MODULE_INIT() instead of NAPI_MODULE() ensures that the resulting | |
// addon can be loaded multiple times within the same Node.js process. | |
NAPI_MODULE_INIT() { | |
// Allocate and initialize our "global static" variables. | |
AddonState* state = malloc(sizeof(*state)); | |
state->callback_function = NULL; | |
state->call_count = 0; | |
// Ensure the state gets freed when this addon is unloaded by attaching it to | |
// the exports object we're about to populate. | |
NAPI_CALL(napi_wrap(env, exports, state, DestroyState, NULL, NULL)); | |
// We pass the addon state into all bindings defined by this addon. | |
napi_property_descriptor properties[] = { | |
{ "setCallback", NULL, SetCallback, NULL, NULL, NULL, napi_enumerable, state }, | |
{ "callCallback", NULL, CallCallback, NULL, NULL, NULL, napi_enumerable, state }, | |
}; | |
// Attach the above-defined properties to the "exports" object. | |
NAPI_CALL(napi_define_properties(env, exports, | |
sizeof(properties) / sizeof(*properties), properties)); | |
return exports; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
'targets': [ | |
{ | |
'target_name': 'binding', | |
'sources': [ 'binding.c' ] | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const addon = require('./build/Release/binding'); | |
console.log("Setting callback"); | |
let callCount = 0; | |
addon.setCallback(() => console.log("Callback called")); | |
callCount = addon.callCallback(); | |
console.log(`callCount: ${callCount}`); | |
callCount = addon.callCallback(); | |
console.log(`callCount: ${callCount}`); | |
console.log("Setting null callback"); | |
addon.setCallback(null); | |
callCount = addon.callCallback(); | |
console.log(`callCount: ${callCount}`); | |
callCount = addon.callCallback(); | |
console.log(`callCount: ${callCount}`); | |
console.log("Setting another callback"); | |
addon.setCallback(() => console.log("Another callback")); | |
callCount = addon.callCallback(); | |
console.log(`callCount: ${callCount}`); | |
callCount = addon.callCallback(); | |
console.log(`callCount: ${callCount}`); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "n-api-multicontext-example", | |
"version": "1.0.0", | |
"description": "", | |
"main": "index.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1", | |
"install": "node-gyp rebuild" | |
}, | |
"author": "", | |
"license": "Apache-2.0", | |
"gypfile": true | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment