-
Notifications
You must be signed in to change notification settings - Fork 1
Node.js native C modules
Overview:
- make project folder as a git repo
- as a Node.js module, it is defined for
npm
in apackage.json
file - write C++ code against the Node.js API
- build it with
node-gyp
via definitions in abinding.gyp
file - test it / wrap it with a
index.js
file
Assuming a module called "addon":
mkdir addon
cd addon
git init
npm init
npm install --save bindings
npm install --save-dev node-gyp
Edit the package.json
to add "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.js
Node-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" ],
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;
}