-
Notifications
You must be signed in to change notification settings - Fork 0
Node.js native C modules
Overview:
- make project folder as a git repo
- as a Node.js module, it is defined for
npmin apackage.jsonfile - write C++ code against the Node.js API
- build it with
node-gypvia definitions in abinding.gypfile - test it / wrap it with a
index.jsfile
Assuming a module called "addon":
mkdir addon
cd addon
git init
npm init
npm install --save bindings
npm install --save-dev node-gypEdit the package.json to add "gypfile": true. You might also want to set a postinstall script to run a test after each build:
{
"name": "<module name>",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"postinstall": "node test.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"bindings": "^1.5.0"
},
"devDependencies": {
"node-gyp": "^6.1.0"
},
"gypfile": true
}Create a binding.gyp file (the code below assumes module name is "addon"):
{
"targets": [{
"target_name": "addon",
"sources": [ "addon.cpp" ],
"defines": [],
"cflags": ["-std=c++11", "-Wall", "-pedantic"],
"include_dirs": [],
"libraries": [],
"dependencies": [],
"conditions": [
['OS=="win"', {}],
['OS=="mac"', {}],
['OS=="linux"', {}],
],
}]
}A minimal addon.cpp:
#include <node_api.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
napi_value Hello(napi_env env, const napi_callback_info info) {
printf("hello\n");
return nullptr;
}
napi_value Goodbye(napi_env env, const napi_callback_info info) {
napi_value msg;
napi_create_string_utf8(env, "ciao", NAPI_AUTO_LENGTH, &msg);
return msg;
}
napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor export_properties[] = {
{
"Hello", nullptr, Hello,
nullptr, nullptr, nullptr, napi_default, nullptr
},
{
"Goodbye", nullptr, Goodbye,
nullptr, nullptr, nullptr, napi_default, nullptr
},
};
assert(napi_define_properties(env, exports,
sizeof(export_properties) / sizeof(export_properties[0]), export_properties) == napi_ok);
return exports;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)A minimal index.js as the entry-point for the module:
const addon = require('bindings')('addon');
// module.exports defines what the module actually exposes to code that loads it:
module.exports = addon;Why module.exports = addon? In fact most native modules use a javascript wrapper, setting index.js as the entry point for the module. This lets you wrap the native code with extra features that are much easier to write in javascript than in C++.
A minimal test.js:
const assert = require("assert")
const addon = require("./index.js");
console.log(addon);npm install
node test.jsNode-gyp is a command-line utility for compiling C/C++ code into binaries. It was designed for building node.js modules, but it can also compile general libraries and executables.
The main methods are node-gyp rebuild to build the module and node-gyp clean to delete all build files again
This file is a JSON configuration to tell node-gyp how to build files. Similar to a makefile or CMakeLists.txt file, this specifies the details of how to compile the code, including things like additional include paths, library paths, defines, linker options, etc., but it does so as a JSON structure rather than a sequence of commands. It has very many options.
Relative paths are OK in a binding.gyp file, and in most cases are relative to the binding.gyp file location. However for some reason, relative library search paths need an extra ../ in front of them.
Node-gyp disables exceptions by default, but if you need them:
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],To specify things according to the OS platform (important for different libraries, for example):
"conditions": [
['OS=="win"', {}],
['OS=="mac"', {}],
['OS=="linux"', {}],
],Does your module depend on a dll? If so, you might get weird 'module could not be found' errors, because node can't find the dll in question. To fix, add a 'copies' section to the binding.gyp. Probably this should be inside the "conditions" section for 'OS=="win"'.
"copies": [{
'destination': './build/Release',
'files': [
"<path to dll>"
]
}],The API reference docs are here -- but they are really a reference, and not a good place to learn from initially.
There is an official repository of examples here
Here's a quick tutorial on the napi API
Note that there is a confusingly similar named node-addon-api, which is just a C++ wrapper around the N-API.
There are several different APIs for developing native (i.e. C/C++) Node.js modules. Until recently, the recommended API was called nan, however this C++ API does not guarantee stability. But nowadays it is recommended instead to use the napi API, which does guarantee ABI stability. There is also a C++ wrapper of this API, with the confusingly-similar name of node-addon-api.
If you want to use the C++ wrapper of napi:
- install it:
npm install --save-dev node-addon-api - add to binding.gyp:
'include_dirs': [ "<!@(node -p \"require('node-addon-api').include\")" ],'dependencies': ["<!(node -p \"require('node-addon-api').gyp\")" ],'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ]
- The C++ file looks like this instead:
#include <napi.h>
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
return exports;
}