DEV Community

Cover image for How to create a native add-on using C++
TheGuildBot for The Guild

Posted on • Edited on • Originally published at the-guild.dev

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

This article was published on Monday, May 8, 2017 by Eytan Manor @ The Guild Blog

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]
Enter fullscreen mode Exit fullscreen mode

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'
}
Enter fullscreen mode Exit fullscreen mode
console.log(obj.foo)
console.log(obj.bar)
console.log(obj.baz)
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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')
})
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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┊});🚫↵
Enter fullscreen mode Exit fullscreen mode

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┊}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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┊}🚫↵
Enter fullscreen mode Exit fullscreen mode

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┊  }
Enter fullscreen mode Exit fullscreen mode

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🚫↵
Enter fullscreen mode Exit fullscreen mode

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┊}
Enter fullscreen mode Exit fullscreen mode

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

npm install nan --save
Enter fullscreen mode Exit fullscreen mode

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)🚫↵
Enter fullscreen mode Exit fullscreen mode

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)🚫↵
Enter fullscreen mode Exit fullscreen mode

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) {
Enter fullscreen mode Exit fullscreen mode

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) {
Enter fullscreen mode Exit fullscreen mode

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) {
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
 ┊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();
Enter fullscreen mode Exit fullscreen mode

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)🚫↵
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
 ┊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();
Enter fullscreen mode Exit fullscreen mode
 ┊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) {
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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┊
Enter fullscreen mode Exit fullscreen mode
 ┊39┊40┊  }
 ┊40┊41┊
 ┊41┊42┊  void Execute () {
+┊  ┊43┊    distance = CalculateDistance(pointA, pointB);
 ┊43┊44┊  }
 ┊44┊45┊
 ┊45┊46┊  void HandleOKCallback () {
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
 ┊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┊};
Enter fullscreen mode Exit fullscreen mode

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┊};🚫↵
Enter fullscreen mode Exit fullscreen mode

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

npm run test
Enter fullscreen mode Exit fullscreen mode

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

sync calculation passed
async calculation passed
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)