How to Create C/C++ Addons in Node

Node.js is great for a lot of reasons, one of which is the speed in which you can build meaningful applications. However, as we all know, this comes at the price of performance (as compared to native code). To get around this, you can write your code to interface with faster code written in C or C++. All we need to do is let Node know where to find this code and how to interface with it.

There are a few ways to solve this problem depending on what level of abstraction you want. We'll start with the lowest abstraction, which is the Node Add-on.

Add-ons

An add-on works by providing the glue between Node and C/C++ libraries. For the typical Node developer this may be a bit complicated as you'll have to get into actually writing C/C++ code to set up the interface. However, between this article and the Node documentation, you should be able to get some simple interfaces working.

There are a few things we need to go over before we can jump in to creating add-ons. First of all, we need to know how to compile (something Node developers happily forget about) the native code. This is done using node-gyp. Then, we'll briefly talk about nan, which helps with handling different Node API versions.

node-gyp

There are a lot of different kinds of processors out there (x86, ARM, PowerPC, etc), and even more operating systems to deal with when compiling your code. Luckily, node-gyp handles all of this for you. As described by their GitHub page, node-gyp is a "cross-platform command-line tool written in Node.js for compiling native add-on modules for Node.js". Essentially, node-gyp is just a wrapper around gyp, which is made by the Chromium team.

The project's README has some great instructions on how to install and use the package, so you should read that for more detail. In short, to use node-gyp you need to do the following.

Go to your project's directory:

$ cd my_node_addon

Generate the appropriate build files using the configure command, which will create either a Makefile (on Unix), or vcxproj (on Windows):

$ node-gyp configure

And finally, build the project:

$ node-gyp build

This will generate a /build directory containing, among other things, the compiled binary.

Even when using higher abstractions like the ffi package, it's still good to understand what is happening under the hood, so I'd recommend you take the time to learn the ins and outs of node-gyp.

nan

nan (Native Abstractions for Node) is an easily overlooked module, but it'll save you hours of frustration. Between Node versions v0.8, v0.10, and v0.12, the V8 versions used went through some big changes (in addition to changes within Node itself), so nan helps hide these changes from you and provides a nice, consistent interface.

This native abstraction works by providing C/C++ objects/functions in the #include <nan.h> header file.

To use it, install the nan package:

$ npm install --save nan

Add these lines to your binding.gyp file:

"include_dirs" : [ 
    "<!(node -e \"require('nan')\")"
]

And you're ready to use the methods/functions from nan.h within your hooks instead of the original #include <node.h> code. I'd highly recommend you use nan. There isn't much point in re-inventing the wheel in this case.

Creating the Add-on

Before starting on your add-on, make sure you take some time to familiarize yourself with the following libraries:

  • The V8 JavaScript C++ library, which is used for actually interfacing with JavaScript (like creating functions, calling objects, etc).
    • NOTE: node.h is the default file suggested, but really nan.h should be used instead
  • libuv, a cross-platform asynchronous I/O library written in C. This library is useful for when performing any type of I/O (opening a file, writing to the network, setting a timer, etc) in your native libraries and you need to make it asynchronous.
  • Internal Node libraries. One of the more important objects to understand is node::ObjectWrap, which most objects derive from.

Throughout the rest of this section, I'll walk you through an actual example. In this case, we'll be creating a hook to the C++ <cmath> library's pow function. Since you should almost always be using nan, that's what I'll be using throughout the examples.

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

For this example, in your add-on project you should have at least these files present:

  • pow.cpp
  • binding.gyp
  • package.json

The C++ file doesn't need to be named pow.cpp, but the name typically reflects either that it is an add-on, or its specific function.

// pow.cpp
#include <cmath>
#include <nan.h>

void Pow(const Nan::FunctionCallbackInfo<v8::Value>& info) {

    if (info.Length() < 2) {
        Nan::ThrowTypeError("Wrong number of arguments");
        return;
    }

    if (!info[0]->IsNumber() || !info[1]->IsNumber()) {
        Nan::ThrowTypeError("Both arguments should be numbers");
        return;
    }

    double arg0 = info[0]->NumberValue();
    double arg1 = info[1]->NumberValue();
    v8::Local<v8::Number> num = Nan::New(pow(arg0, arg1));

    info.GetReturnValue().Set(num);
}

void Init(v8::Local<v8::Object> exports) {
    exports->Set(Nan::New("pow").ToLocalChecked(),
                 Nan::New<v8::FunctionTemplate>(Pow)->GetFunction());
}

NODE_MODULE(pow, Init)

Note there is no semicolon (;) at the end of NODE_MODULE. This is done intentionally since NODE_MODULE is not actually a function - it's a macro.

The above code may seem a bit daunting at first for those that haven't written any C++ in a while (or ever), but it really isn't too hard to understand. The Pow function is the meat of the code where we check the number of arguments passed, the types of the arguments, call the native pow function, and return the result to the Node application. The info object contains everything about the call that we need to know, including the arguments (and their types) and a place to return the result.

The Init function mostly just associates the Pow function with the
"pow" name, and the NODE_MODULE macro actually handles registering the add-on with Node.

The package.json file is not much different from a normal Node module. Although it doesn't seem to be required, most Add-on modules have "gypfile": true set within them, but the build process seems to still work fine without it. Here is what I used for this example:

{
  "name": "addon-hook",
  "version": "0.0.0",
  "description": "Node.js Add-on Example",
  "main": "index.js",
  "dependencies": {
    "nan": "^2.0.0"
  },
  "scripts": {
    "test": "node index.js"
  }
}

Next, this code needs to be built into a pow.node file, which is the binary of the Add-on. To do this, you'll need to tell node-gyp what files it needs to compile and the binary's resulting filename. While there are many other options/configurations you can use with node-gyp, for this example we don't need a whole lot. The binding.gyp file can be as simple as:

{
    "targets": [
        {
            "target_name": "pow",
            "sources": [ "pow.cpp" ],
            "include_dirs": [
                "<!(node -e \"require('nan')\")"
            ]
        }
    ]
}

Now, using node-gyp, generate the appropriate project build files for the given platform:

$ node-gyp configure

And finally, build the project:

$ node-gyp build

This should result in a pow.node file being created, which will reside in the build/Release/ directory. To use this hook in your application code, just require in the pow.node file (sans the .node extension):

var addon = require('./build/Release/pow');

console.log(addon.pow(4, 2));		// Prints '16'

Node Foreign Function Interface

Note: The ffi package is formerly known as node-ffi. Be sure to add the newer ffi name to your dependencies to avoid a lot of confusion during npm install :)

While the Add-on functionality provided by Node gives you all the flexibility you need, not all developers/projects will need it. In many cases an abstraction like ffi will do just fine, and typically require very little to no C/C++ programming.

ffi only loads dynamic libraries, which can be limiting for some, but it also makes the hooks much easier to set up.

var ffi = require('ffi');

var libm = ffi.Library('libm', {
    'pow': [ 'double', [ 'double', 'double' ] ]
});

console.log(libm.pow(4, 2));	// 16

The above code works by specifying the library to load (libm), and specifically which methods to load from that library (pow). The [ 'double', [ 'double', 'double' ] ] line tells ffi what the return type and parameters of the method is, which in this case is two double parameters and a double returned.

Conclusion

While it may seem intimidating at first, creating an Add-on really isn't too bad after you've had the chance to work through a small example like this on your own. When possible, I'd suggest hooking into a dynamic library to make creating the interface and loading the code much easier, although for many projects this may not be possible or the best choice.

Are there any examples of libraries you'd like to see bindings for? Let us know in the comments!

Last Updated: November 15th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms