Skip to content

Node.js native C modules

Graham Wakefield edited this page Jun 20, 2019 · 25 revisions

Writing Node.js modules in C/C++ ("native modules")

Overview:

  • make project folder as a git repo
  • as a Node.js module, it is defined for npm in a package.json file
  • write C++ code against the Node.js API
  • build it with node-gyp via definitions in a binding.gyp file
  • test it / wrap it with a index.js file

Starting a new module project:

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

Build & test:

npm install
node test.js

Node-gyp

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

Bindings.gyp

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.

Bindings.gyp reference docs

Tips

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" ],

N-API, AKA Node.js C API, "napi" or "node_api.h"

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.

Why use 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.


The C++ 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;
}

Introductory slides here

There's a full introduction here

Clone this wiki locally