How to create a native add-on using C++

Eytan Manor

In this tutorial we’re going to go through the basics on how to write a native add-on to NodeJS using C++, one of the platform’s most powerful capabilities of which most web/JS developers now a days are not even familiar with.

For somewhat there is a vacuum which got created around C++ in the recent years, people are so scared from a low-level back-end programming, why should they even deal with memory allocation dilemmas when there are all these interpretation languages like Python, Ruby, and JavaScript who can do that for us? Well folks, once you will realize that all these single web-page application third-party libraries and frameworks are all recycled and repetitive, and you will start doing some real shit by optimizing some heavy-ass machine algorithms, you will realize that performance is critic, a title which doesn’t really suit NodeJS, with all the love and respect. So why do I even bother making this tutorial? Because people are unaware of many features in the programming world, and the beauty of algorithmics. And why am I even approaching NodeJS developers? Because they have the biggest, most-popular community of all, they have a lot to learn, but they are very open-minded, and that’s the most important thing. You don’t have to be a C++ developer not at all, but if I can at least catch a small part of your attention I will be overwhelmed.

We will create a very small and simple module which calculates the distance between two dots, it can do it either synchronously or asynchronously. The algorithm for calculating the distance between a given pointA and pointB in a 2D Cartesian coordinate system looks like this:

√[(pointAX - pointBX)^2 + (pointAY - pointBY)^2]

Probably not too complicated, kicks me back to the glamorous days in high-school. If put in JavaScript it should take around 10 seconds right? Well in a native NodeJS add-on it should be a bit longer. Of-course when it comes to small logics you shouldn’t make too much effort, because it will consume more power only converting all JS objects into C++ native structures. But imagine you would like to run a blob-detection algorithm and you would like to calculate the center of mass of multiple blobs, in C++ it would be much faster, especially when using shaders. Anyway that’s not the point of this tutorial, the point is that you will be provided with the necessary tools so next time if you would like to write some heavy logic, you will know how to do it.

We will start with a brief introduction for Google’s v8 engine and some practical examples on how to use it, this will help us to get start, and then we will write our add-on. Let’s begin then shall we?


Introduction to V8

The v8 JavaScript Engine is an open source JavaScript engine developed by The Chromium Project for the Google Chrome web browser. It is intended to be used both in a browser and as a standalone high-performance engine that can be integrated into independent projects like Couchbase, MongoDB and Node.js. There lots of benchmarks out there but I will not bore you with diagrams, we all know that this engine has proven itself to be worthy, and it is being used world-widely and probably for a good reason.

“Ambition is a dream with a v8 engine” -Elvis Presley.

If you’re a JavaScript developer you must be familiar with the event-loop and the scoping system of v8, so it’s a good thing that you understand the concept, but you never got to actually look at its source code and explicitly use it. A detailed documentation for v8’s different API versions is available here. I assume that you don’t bother reading any of my references that I provide along this tutorial (As a lazy blog-reader myself), I will post here that first thing you’re going to see once you enter v8’s documentation web-site:

Throughout history v8 have changed a lot, and it wore many forms. As a result, add-ons are not usable across different versions of the platform since each one supports a different API, which will break our process during run-time. In NodeJS team they came up with a very convenient solution called Nan. Nan stands for “Native Abstractions for NodeJS” and is basically a header file filled with macro and utility goodness for making add-on development for NodeJS easier across all versions of v8, without inspecting NODE_MODULE_VERSION macro all the time. In this tutorial I’m going to refer both of them as if they are bundled in a single framework.

Eventually JavaScript is just a rehash of v8, everything you know so far is still valid, but it uses a different idiom. To prevent some misconceptions, here are some important points regards JavaScript’s equivalents which I think you should follow:

Scopes and Variables

In v8 a scope is referred as Isolate (v8::Isolate) and a variable is referred as Local (v8::Local). A local is a pointer to an object. All v8 objects are accessed using locals, they are necessary because of the way the v8 garbage collector works. An isolate can be thought of as a container for any number of locals. When you’ve finished with your locals, instead of deleting each one individually you can simply delete their scope.

JavaScript

let obj = {
  foo: 'foo',
  bar: 'bar',
  baz: 'baz'
}
console.log(obj.foo)
console.log(obj.bar)
console.log(obj.baz)

C++

using Nan::New;
using std::cendl;
using std::cout;
using v8::Local;
using v8::Object;
using v8::String;
 
Local<Object> obj = New<Object>();
 
obj->Set(New<String>("foo").ToLocalChecked(), New<String>("foo").ToLocalChecked());
obj->Set(New<String>("bar").ToLocalChecked(), New<String>("bar").ToLocalChecked());
obj->Set(New<String>("baz").ToLocalChecked(), New<String>("baz").ToLocalChecked());
 
cout << obj->Get(New<String>("foo").ToLocalChecked()) << cendl;
cout << obj->Get(New<String>("bar").ToLocalChecked()) << cendl;
cout << obj->Get(New<String>("baz").ToLocalChecked()) << cendl;

Asynchronous Callbacks

Asynchronous logic can be implemented using the AsyncWorker (Nan::AsyncWorker) and invoked by AsyncQueueWorker (Nan::AsyncQueueWorker). Thanks to these two you can have much of the annoying asynchronous queuing and handling taken care of for you. It can even store arbitrary V8 objects for you and have them persist while the asynchronous work is in progress.

JavaScript

setImmediate(() => {
  callback(null, 'result')
})

C++

using Nan::AsyncQueueWorker;
using Nan::AsyncWorker;
using Nan::HandleScope;
using Nan::New;
using Nan::Null;
using std::string;
using v8::Local;
using v8::String;
 
class ResultWorker : AsyncWorker {
 private:
  string* result
 
 public:
  ResultWorker(Callback* callback) : AsyncWorker(callback) {}
 
  ~ResultDistance() {
    delete result;
  }
 
  // Executed inside the worker-thread.
  // It is not safe to access V8, or V8 data structures
  // here, so everything we need for input and output
  // should go on 'this'.
  void Execute () {
    result = new string("result");
  }
 
  // Executed when the async work is complete.
  // This function will be run inside the main event loop
  // so it is safe to use V8 again.
  void HandleOKCallback () {
    HandleScope scope;
    Local<Value> argv[] = {
      Null(),
      New<String>(result).ToLocalChecked()
    };
    callback->Call(2, argv);
  }
};
 
AsyncQueueWorker(new ResultWorker(callback));

Modules Registration and Methods Definition

v8 and Nan provide us with some handy macros (NODE_MODULE, NAN_MODULE_INIT, NAN_METHOD and NODE_SET_METHOD) which will help us register a new NodeJS module and define its methods. This might be confusing for some, since we can’t see the function’s signature it would appear as if variables are just being magically created in the stack, but once the macros are being pre-processed they will just turn into ordinary functions. In the example below I commented the original signature so you can have more clew on what’s going on.

JavaScript

exports.fn = (a, b) => a + b

C++

using Nan::To;
using v8::Local;
using v8::Object;
 
// void Fn(FunctionCallbackInfo<Value>& info)
NAN_METHOD(Fn) {
  double a = To<double>(info[0]).FromJust();
  double b = To<double>(info[1]).FromJust();
 
  info.GetReturnValue().Set(a + b);
}
 
// void Init(Local<Object> target)
NAN_MODULE_INIT(Init) {
  NODE_SET_METHOD(target, Fn);
}
 
// First argument would be the entry file's name
NODE_MODULE(addon, Init);

As you can see when dealing with v8 explicitly is a time-consuming process which requires you to do lots of extra-work. With that said, keep in mind that only a small portion of your code is going to interact with the engine since the core logic should be written using native C++ and other third-party libraries. You always need to find the right balance. Always make sure that your add-on doesn’t require too much data to be passed otherwise the conversion process is gonna be hard and inefficient, and think twice before you choose this approach for the sake of simplicity. Overall the estimated optimization should be around 150% and up, depends on the task, first check your JavaScript code snippet, check for unnecessary logic and optimize it, and if you’re really sure that it is fully optimized, and you’re still striving for more performance, only then move to C++.

So far I went through the very basics which will help you create this bridge between the two platforms. The v8 lacks of detailed documentation, tutorials and examples. Nan however is a bit more documented IMHO, so when I approach the API documentation I would start from there, and if I didn’t find anything useful I would look at v8’s latest API docs. It’s not a hard material to learn but it’s different, so it might be a bit challenging for some, but remember, practice practice practice.

Speaking of practice, let’s move on to the next step where we going to implement our first add-on for distance calculation between two points.


Creating the Add-On

In this step we will base our development process on the TDD methodology, so you will have a chance to look at the final target API that we desire. We will start by writing a test file:

Add Test File

Added .npmignore

+┊ ┊1┊test.js

Added test.js

+┊  ┊ 1┊const Distance = require('.');
+┊  ┊ 2┊
+┊  ┊ 3┊let result;
+┊  ┊ 4┊let pointA = { x: 0, y: 0 };
+┊  ┊ 5┊let pointB = { x: 3, y: 4 };
+┊  ┊ 6┊
+┊  ┊ 7┊result = Distance.calculate.sync(pointA, pointB);
+┊  ┊ 8┊
+┊  ┊ 9┊if (result !== 5) throw Error(
+┊  ┊10┊  '#Sync: Result expected to equal 5 but instead got ' + result
+┊  ┊11┊);
+┊  ┊12┊
+┊  ┊13┊console.log('sync calculation passed');
+┊  ┊14┊
+┊  ┊15┊result = Distance.calculate.async(pointA, pointB, (err, result) => {
+┊  ┊16┊  if (err) throw err;
+┊  ┊17┊
+┊  ┊18┊  if (result !== 5) throw Error(
+┊  ┊19┊    '#Async: Result expected to equal 5 but instead got ' + result
+┊  ┊20┊  );
+┊  ┊21┊
+┊  ┊22┊  console.log('async calculation passed');
+┊  ┊23┊});🚫↵

And the following NPM script should execute it:

Add npm Test Script

Changed package.json

 ┊ 6┊ 6┊  "repository": {
 ┊ 7┊ 7┊    "type": "git",
 ┊ 8┊ 8┊    "url": "https://github.com/DAB0mB/node-distance-addon.git"
+┊  ┊ 9┊  },
+┊  ┊10┊  "scripts": {
+┊  ┊11┊    "test": "node test"
 ┊ 9┊12┊  }
 ┊10┊13┊}

Like I said in the introduction, it’s a simple module which can calculate the distance between 2 given points. calculate.sync can do it synchronously and calculate.async can do it asynchronously. Now that you got the idea we will start configuring our add-on.

The first thing you’ll need to do is to make sure that you have node-gyp installed:

sudo npm install -g node-gyp

node-gyp is also dependent on many other packages, so before you go any further please take a look at the official installation instructions in their README.md file.

Assuming that you have installed everything properly, we will now need to create a binding.gyp file:

Create binding.gyp File

Added binding.gyp

+┊  ┊ 1┊{
+┊  ┊ 2┊  "targets": [
+┊  ┊ 3┊    {
+┊  ┊ 4┊      "target_name": "distance",
+┊  ┊ 5┊      "sources": [
+┊  ┊ 6┊        "src/distance.cc"
+┊  ┊ 7┊      ],
+┊  ┊ 8┊      "include_dirs": ["<!(node -e \"require('nan')\")"]
+┊  ┊ 9┊    }
+┊  ┊10┊  ]
+┊  ┊11┊}🚫↵

GYP stands for ‘Generate Your Project’ and was created by the Chromium team as a configuration file for building native projects. The configuration show above should be a good template for any future add-on you’re looking to develop. Let’s take a deeper look at it:

  • target_name - Specifies the output dir of our add-on, in which case it should be build/Release/distance
  • sources - Should include all the cpp files that are associated with you add-on.
  • include_dirs - Additional dirs that should be included when building the add-on. If you’ll run the given script in the terminal you’ll get the node-module path for Nan, a library which we’re interested in during the build process.

More information about GYP configuration can be found here.

Be sure to also add the specified flag to the package.json which basically says ‘Hey, I have a GYP file which should be taken into consideration as well’:

Create binding.gyp File

Changed package.json

 ┊ 7┊ 7┊    "type": "git",
 ┊ 8┊ 8┊    "url": "https://github.com/DAB0mB/node-distance-addon.git"
 ┊ 9┊ 9┊  },
+┊  ┊10┊  "gypfile": true,
 ┊10┊11┊  "scripts": {
 ┊11┊12┊    "test": "node test"
 ┊12┊13┊  }

Now we will add the following NPM scripts so whenever we run npm run build our project will be built:

Add npm Build Scripts

Changed .gitignore

+┊ ┊1┊build
 ┊1┊2┊node_modules🚫↵

Changed package.json

 ┊ 9┊ 9┊  },
 ┊10┊10┊  "gypfile": true,
 ┊11┊11┊  "scripts": {
+┊  ┊12┊    "pre-publish": "npm run build",
+┊  ┊13┊    "build": "node-gyp rebuild",
+┊  ┊14┊    "test": "npm run build && node test"
 ┊13┊15┊  }
 ┊14┊16┊}

The only thing left to do before jumping into implementation would be installing Nan:

npm install nan --save

The basis for build process is set. We will create the entry file for our add-on:

Create Add-On Entry File

Added src/distance.cc

+┊ ┊1┊#include <nan.h>
+┊ ┊2┊#include <v8.h>
+┊ ┊3┊
+┊ ┊4┊NAN_MODULE_INIT(Init) {
+┊ ┊5┊
+┊ ┊6┊}
+┊ ┊7┊
+┊ ┊8┊NODE_MODULE(distance, Init)🚫↵

Every add-on should start with these two macro calls. They are both compiled into a piece of code which will register our module with ease. The NODE_MODULE macro template accepts the name of the target as the first argument (That one we set as target_name in the GYP file, remember?) and the initialization method for our module. The NAN_MODULE_INITgenerates a function with the given name. It accepts target as the first argument which is equivalent to Node.js’ exports. Now we will create our first method stub for a synchronous distance calculation:

Create CalculateSync Method Stub

Changed src/distance.cc

 ┊ 1┊ 1┊#include <nan.h>
 ┊ 2┊ 2┊#include <v8.h>
 ┊ 3┊ 3┊
+┊  ┊ 4┊NAN_METHOD(CalculateSync) {
+┊  ┊ 5┊
+┊  ┊ 6┊}
 ┊ 5┊ 7┊
+┊  ┊ 8┊NAN_MODULE_INIT(Init) {
+┊  ┊ 9┊  NAN_EXPORT(target, CalculateSync);
 ┊ 6┊10┊}
 ┊ 7┊11┊
 ┊ 8┊12┊NODE_MODULE(distance, Init)🚫↵

We exported the CalculateSync by using the NAN_EXPORT macro, and we used NAN_METHOD to define a new node-valid function. It accepts info as the first argument, and it is the same as JavaScript’s arguments vector. We already know which arguments this function should accept, that’s why I followed TDD methodology from the first place:

Destructure Arguments Vector

Changed src/distance.cc

 ┊ 1┊ 1┊#include <nan.h>
 ┊ 2┊ 2┊#include <v8.h>
 ┊ 3┊ 3┊
+┊  ┊ 4┊using Nan::To;
+┊  ┊ 5┊using v8::Local;
+┊  ┊ 6┊using v8::Object;
 ┊ 5┊ 7┊
+┊  ┊ 8┊NAN_METHOD(CalculateSync) {
+┊  ┊ 9┊  Local<Object> js_pointA = To<Object>(info[0]).ToLocalChecked();
+┊  ┊10┊  Local<Object> js_pointB = To<Object>(info[1]).ToLocalChecked();
 ┊ 6┊11┊}
 ┊ 7┊12┊
 ┊ 8┊13┊NAN_MODULE_INIT(Init) {

We use the To() function to convert the first argument into the desired type, and then we call the method ToLocalChecked(). This method is simply going to convert the result into v8’s Local, unless the argument is undefined, in which case an error is going to be thrown. I like to prefix JS object with a js_ so I know with what kind variable I’m dealing with. We should have two points containing x and y fields. Let’s try to extract them out of the arguments vector and convert them into native C++ structures:

Convert JS Objects to Native C++ Structures

Changed src/distance.cc

 ┊ 1┊ 1┊#include <nan.h>
 ┊ 2┊ 2┊#include <v8.h>
 ┊ 3┊ 3┊
+┊  ┊ 4┊using Nan::New;
 ┊ 4┊ 5┊using Nan::To;
 ┊ 5┊ 6┊using v8::Local;
 ┊ 6┊ 7┊using v8::Object;
+┊  ┊ 8┊using v8::String;
+┊  ┊ 9┊
+┊  ┊10┊struct Point {
+┊  ┊11┊  double x;
+┊  ┊12┊  double y;
+┊  ┊13┊};
 ┊ 7┊14┊
 ┊ 8┊15┊NAN_METHOD(CalculateSync) {
 ┊ 9┊16┊  Local<Object> js_pointA = To<Object>(info[0]).ToLocalChecked();
 ┊10┊17┊  Local<Object> js_pointB = To<Object>(info[1]).ToLocalChecked();
+┊  ┊18┊
+┊  ┊19┊  Point* pointA = new Point();
+┊  ┊20┊  pointA->x = To<double>(js_pointA->Get(New<String>("x").ToLocalChecked())).FromJust();
+┊  ┊21┊  pointA->y = To<double>(js_pointA->Get(New<String>("y").ToLocalChecked())).FromJust();
+┊  ┊22┊
+┊  ┊23┊  Point* pointB = new Point();
+┊  ┊24┊  pointB->x = To<double>(js_pointB->Get(New<String>("x").ToLocalChecked())).FromJust();
+┊  ┊25┊  pointB->y = To<double>(js_pointB->Get(New<String>("y").ToLocalChecked())).FromJust();
 ┊11┊26┊}
 ┊12┊27┊
 ┊13┊28┊NAN_MODULE_INIT(Init) {

Then again we convert the To() function to convert the result into the desired data-type, only this time it’s a primitive, so we use FromJust() instead of ToLocalChecked(). Note that v8 only uses double precision rather than a floating point. We can fetch properties from a given JS object with ease using the Get() method. Pay attention to use the -> rather than a period because remember, a Local is actually a pointer! It is not the actual object.

Now all is left to do is defining the return value. Keep in mind that the value should be returned through v8’s current scope, not natively, so using the return keyword would be useless. The return value can actually be defined through the provided info argument, like this:

Add Return Value to CalculateSync Method

Changed src/distance.cc

 ┊23┊23┊  Point* pointB = new Point();
 ┊24┊24┊  pointB->x = To<double>(js_pointB->Get(New<String>("x").ToLocalChecked())).FromJust();
 ┊25┊25┊  pointB->y = To<double>(js_pointB->Get(New<String>("y").ToLocalChecked())).FromJust();
+┊  ┊26┊
+┊  ┊27┊  info.GetReturnValue().Set(CalculateDistance(pointA, pointB));
 ┊26┊28┊}
 ┊27┊29┊
 ┊28┊30┊NAN_MODULE_INIT(Init) {

And of-course it requires us to add the core distance calculation method:

Add Core Distance Calculation Method

Changed src/distance.cc

+┊  ┊ 1┊#include <cstdlib>
+┊  ┊ 2┊#include <cmath>
 ┊ 1┊ 3┊#include <nan.h>
 ┊ 2┊ 4┊#include <v8.h>
 ┊ 3┊ 5┊
 ┊ 4┊ 6┊using Nan::New;
 ┊ 5┊ 7┊using Nan::To;
+┊  ┊ 8┊using std::pow;
+┊  ┊ 9┊using std::sqrt;
 ┊ 6┊10┊using v8::Local;
 ┊ 7┊11┊using v8::Object;
 ┊ 8┊12┊using v8::String;
 ┊12┊16┊  double y;
 ┊13┊17┊};
 ┊14┊18┊
+┊  ┊19┊double CalculateDistance(Point* pointA, Point* pointB) {
+┊  ┊20┊  return sqrt(pow(pointA->x - pointB->x, 2) + pow(pointA->y - pointB->y, 2));
+┊  ┊21┊}
+┊  ┊22┊
 ┊15┊23┊NAN_METHOD(CalculateSync) {
 ┊16┊24┊  Local<Object> js_pointA = To<Object>(info[0]).ToLocalChecked();
 ┊17┊25┊  Local<Object> js_pointB = To<Object>(info[1]).ToLocalChecked();

This is it for the synchronous calculation. Now we will add an async version of it. We will start by creating a method with everything we learned so far until the point where we have to return the result:

Create CalculateAsync Method with Basic Deconstructuring

Changed src/distance.cc

 ┊35┊35┊  info.GetReturnValue().Set(CalculateDistance(pointA, pointB));
 ┊36┊36┊}
 ┊37┊37┊
+┊  ┊38┊NAN_METHOD(CalculateAsync) {
+┊  ┊39┊  Local<Object> js_pointA = To<Object>(info[0]).ToLocalChecked();
+┊  ┊40┊  Local<Object> js_pointB = To<Object>(info[1]).ToLocalChecked();
+┊  ┊41┊
+┊  ┊42┊  Point* pointA = new Point();
+┊  ┊43┊  pointA->x = To<double>(js_pointA->Get(New<String>("x").ToLocalChecked())).FromJust();
+┊  ┊44┊  pointA->y = To<double>(js_pointA->Get(New<String>("y").ToLocalChecked())).FromJust();
+┊  ┊45┊
+┊  ┊46┊  Point* pointB = new Point();
+┊  ┊47┊  pointB->x = To<double>(js_pointB->Get(New<String>("x").ToLocalChecked())).FromJust();
+┊  ┊48┊  pointB->y = To<double>(js_pointB->Get(New<String>("y").ToLocalChecked())).FromJust();
+┊  ┊49┊}
+┊  ┊50┊
 ┊38┊51┊NAN_MODULE_INIT(Init) {
 ┊39┊52┊  NAN_EXPORT(target, CalculateSync);
+┊  ┊53┊  NAN_EXPORT(target, CalculateAsync);
 ┊40┊54┊}
 ┊41┊55┊
 ┊42┊56┊NODE_MODULE(distance, Init)🚫↵

Here’s the different part. We don’t wanna simply return the value, we want to make the calculations in parallel with the event loop, and once we’re finished we will interact with it once again. In our model there are two threads. The first thread is the event loop thread, and the second thread will be a worker thread managed by Nan, the library supports asynchronous I/O in NodeJS. Let’s start implementing and I will give some more explanations as we go further:

Queue Distance Worker

Changed src/distance.cc

 ┊ 3┊ 3┊#include <nan.h>
 ┊ 4┊ 4┊#include <v8.h>
 ┊ 5┊ 5┊
+┊  ┊ 6┊using Nan::AsyncQueueWorker;
+┊  ┊ 7┊using Nan::AsyncWorker;
+┊  ┊ 8┊using Nan::Callback;
 ┊ 6┊ 9┊using Nan::New;
 ┊ 7┊10┊using Nan::To;
 ┊ 8┊11┊using std::pow;
 ┊ 9┊12┊using std::sqrt;
+┊  ┊13┊using v8::Function;
 ┊10┊14┊using v8::Local;
 ┊11┊15┊using v8::Object;
 ┊12┊16┊using v8::String;
 ┊38┊42┊NAN_METHOD(CalculateAsync) {
 ┊39┊43┊  Local<Object> js_pointA = To<Object>(info[0]).ToLocalChecked();
 ┊40┊44┊  Local<Object> js_pointB = To<Object>(info[1]).ToLocalChecked();
+┊  ┊45┊  Callback* callback = new Callback(info[2].As<Function>());
 ┊41┊46┊
 ┊42┊47┊  Point* pointA = new Point();
 ┊43┊48┊  pointA->x = To<double>(js_pointA->Get(New<String>("x").ToLocalChecked())).FromJust();
 ┊46┊51┊  Point* pointB = new Point();
 ┊47┊52┊  pointB->x = To<double>(js_pointB->Get(New<String>("x").ToLocalChecked())).FromJust();
 ┊48┊53┊  pointB->y = To<double>(js_pointB->Get(New<String>("y").ToLocalChecked())).FromJust();
+┊  ┊54┊
+┊  ┊55┊  AsyncQueueWorker(new DistanceWorker(callback, pointA, pointB));
 ┊49┊56┊}
 ┊50┊57┊
 ┊51┊58┊NAN_MODULE_INIT(Init) {

Here we fetch the third argument which is the callback. We wrap it with Nan’s Callback, which will make sure it is not garbage collected once the scope is being deleted, we want it to keep living until it’s not relevant. At the bottom of the method, instead of returning a value explicitly, we queue our DistanceWorker into the workers queue. On that note, let’s implement the DistanceWorker:

Create DistanceWorker with a Constructor and a Deconstructor

Changed src/distance.cc

 ┊24┊24┊  return sqrt(pow(pointA->x - pointB->x, 2) + pow(pointA->y - pointB->y, 2));
 ┊25┊25┊}
 ┊26┊26┊
+┊  ┊27┊class DistanceWorker : public AsyncWorker {
+┊  ┊28┊ private:
+┊  ┊29┊  Point* pointA;
+┊  ┊30┊  Point* pointB;
+┊  ┊31┊
+┊  ┊32┊ public:
+┊  ┊33┊  DistanceWorker(Callback* callback, Point* pointA, Point* pointB) :
+┊  ┊34┊    AsyncWorker(callback), pointA(pointA), pointB(pointB) {}
+┊  ┊35┊
+┊  ┊36┊  ~DistanceWorker() {
+┊  ┊37┊    delete pointA;
+┊  ┊38┊    delete pointB;
+┊  ┊39┊  }
+┊  ┊40┊
+┊  ┊41┊  void Execute () {
+┊  ┊42┊
+┊  ┊43┊  }
+┊  ┊44┊
+┊  ┊45┊  void HandleOKCallback () {
+┊  ┊46┊
+┊  ┊47┊  }
+┊  ┊48┊};
+┊  ┊49┊
 ┊27┊50┊NAN_METHOD(CalculateSync) {
 ┊28┊51┊  Local<Object> js_pointA = To<Object>(info[0]).ToLocalChecked();
 ┊29┊52┊  Local<Object> js_pointB = To<Object>(info[1]).ToLocalChecked();

AsyncWorker is an abstract class that you can subclass to have much of the annoying asynchronous queuing and handling taken care of for you. It can even store arbitrary v8 objects for you and have them persist while the asynchronous work is in progress. The execute() method is being executed inside the worker-thread. It is not safe to access V8, or V8 data structures there, so everything we need for input and output should go on ‘this’. The HandleOKCallback() method is executed when the async work is complete. This function will be run inside the main event loop, so it is safe to use v8 again. Let’s implement the core distance calculation on the worker thread:

Execute Distance Calculation

Changed src/distance.cc

 ┊26┊26┊
 ┊27┊27┊class DistanceWorker : public AsyncWorker {
 ┊28┊28┊ private:
+┊  ┊29┊  double distance;
 ┊29┊30┊  Point* pointA;
 ┊30┊31┊  Point* pointB;
 ┊31┊32┊
 ┊39┊40┊  }
 ┊40┊41┊
 ┊41┊42┊  void Execute () {
+┊  ┊43┊    distance = CalculateDistance(pointA, pointB);
 ┊43┊44┊  }
 ┊44┊45┊
 ┊45┊46┊  void HandleOKCallback () {

And handle a successful invocation once the calculation is finished:

Handle Successful Invokation

Changed src/distance.cc

 ┊ 6┊ 6┊using Nan::AsyncQueueWorker;
 ┊ 7┊ 7┊using Nan::AsyncWorker;
 ┊ 8┊ 8┊using Nan::Callback;
+┊  ┊ 9┊using Nan::HandleScope;
 ┊ 9┊10┊using Nan::New;
+┊  ┊11┊using Nan::Null;
 ┊10┊12┊using Nan::To;
 ┊11┊13┊using std::pow;
 ┊12┊14┊using std::sqrt;
 ┊13┊15┊using v8::Function;
 ┊14┊16┊using v8::Local;
+┊  ┊17┊using v8::Number;
 ┊15┊18┊using v8::Object;
 ┊16┊19┊using v8::String;
+┊  ┊20┊using v8::Value;
 ┊17┊21┊
 ┊18┊22┊struct Point {
 ┊19┊23┊  double x;
 ┊44┊48┊  }
 ┊45┊49┊
 ┊46┊50┊  void HandleOKCallback () {
+┊  ┊51┊    HandleScope scope;
 ┊47┊52┊
+┊  ┊53┊    Local<Value> argv[] = {
+┊  ┊54┊      Null(),
+┊  ┊55┊      New<Number>(distance)
+┊  ┊56┊    };
+┊  ┊57┊
+┊  ┊58┊    callback->Call(2, argv);
 ┊48┊59┊  }
 ┊49┊60┊};

Normally, when defining a NodeJS method (NAN_METHOD macro) a scope will be created for us automatically. In this function’s context there is no scope exist, so we will have to create it using the HandleScope deceleration (The current scope is stored globally so even though we don’t use it explicitly, v8 and Nan know what to do). We also created an arguments vector as the return value, following Node.js’ conventions, the first argument would be the error and the second argument would be the result.

This is it! Finally, we will transform the add-on into a nicer looking node-module:

Transform Add-On into a Nicer Looking Node Module

Added index.js

+┊ ┊1┊const Distance = require('./build/Release/distance');
+┊ ┊2┊
+┊ ┊3┊exports.calculate = {
+┊ ┊4┊  sync: Distance.CalculateSync,
+┊ ┊5┊  async: Distance.CalculateAsync
+┊ ┊6┊};🚫↵

And now, let’s run our small test to see that it works, using the following command:

npm run test

If everything went well, you should have the following messages printed to the terminal:

sync calculation passed
async calculation passed

What’s Next?

You’ve just learned the very basics of how to use C++ within NodeJS. There’s a lot more to learn when it comes to building an add-on, and I’m not just talking about learning v8 and Nan’s API. Think about the possibilities, the C++ community have been developed for years and there are so much great libraries out there that are not necessarily relevant to NodeJS due to its efficiency, like Boost, OpenCV, CGAL and many more.

Join our newsletter

Want to hear from us when there's something new? Sign up and stay up to date!

By subscribing, you agree with Beehiiv’s Terms of Service and Privacy Policy.

Recent issues of our newsletter