A simple guide to load C/C++ code into Node.js JavaScript Applications

By Uday Hiwarale

An Application Binary Interface is a way for a program to communicate with other compiled programs. This is a lot similar to an API but in the case of ABI, we are interacting with a binary file and accessing memory addresses to look for symbols like numbers, objects, classes, functions, etc.

But, what ABI has to do with Native Addons? The compiled DLL file (Native Addon) when loaded into Node.js runtime actually communicates with Node.js using an ABI provided by the Node.js to access/register values and to perform tasks through APIs exposed by Node.js and its libraries.

For example, a Native Addon might want to add some values into exports object like a function with the name sayHello which returns a Hello World string. It could also access Libuv API to perform an asynchronous task by creating a separate thread and run a callback function when the task is done.

This is all done through ABI provided by the Node.js itself. Hence, when the Native Addon is compiled, it needs to have the idea of what APIs are exposed by the Node.js itself and its libraries.

Typically, when we compile a C or C++ program into a DLL, we use some meta-data files called header files, which ends with .h extension. These header files are provided by Node.js and its libraries and they contain the declarations of the APIs exposed by the Node.js and its libraries.

💡 You can read about header files from this documentation.

A typical C or C++ program can import these header files using #include preprocessor directive, for example, #include<v8.h> and use the declarations to write a meaningful program that can be executed by Node.

You will be able to understand Dynamically Linked Libraries in depth from the below example I’ve created in C++. However, the way Native Addons work is more complex in nature than this example.

(Source: https://gist.github.com/thatisuday/e9d402a889d26a9b88e034027b27f76b)

There are different ways to utilize these header files. Basically, there are four ways we can write a C or C++ program that can be compiled to run by Node.js, however, they all do not produce the same output.

💡 You can read more about ABI and ABI Stability from this documentation on Node.js official documentation guide. I advise you to go through this document and understand how ABI actually works in nutshell.

1. Using Core Implementation

We can use header files provided by Node.js and its libraries. For example, v8.h contains an internal implementation of the V8 engine and uv.h provides API for the Libuv library.

💡 These header files and other indirect dependencies are located inside /include/node/ folder of your Node.js installation directory.

However, doing this we are adding tight dependency between our Native Addon and internal implementation of Node.js and its libraries.

Since the internal implementation of the libraries used by the Node.js could change and so these header files, a Native Addon compiled for the older version of Node.js would become incompatible for the ABI released with the new version of the Node.js.

To maintain ABI stability, it becomes the responsibility of the Addon maintainers to rewrite, build and publish the new version of this Native Module when a new version of Node.js is launched.

This is not a very reliable approach. Hence NAN module was introduced which creates an abstraction layer between Native Addon and internal implementation of Node.js and its libraries.

In addition to these drawbacks, only Native Addons written in C++ can be used with this approach. You can take a look at examples of writing Native Addons with this approach from Node.js official documentation.

This way of developing Native Addons is not recommended and there are better alternative ways which are explained below.

2. Using Native Abstractions for Node (NAN)

NAN project was the initial effort to abstract the internal implementation of Node.js and the V8 engine by adding C++ wrappers around their APIs.

This is basically is an NPM module which can be installed using npm install command and it contains nan.h header file which contains abstracted definitions of APIs exposed by Node.js and V8 library.

However, NAN does not completely abstract V8 API and does not provide full support for all the libraries used inside Node.js.

NAN is not a core part of the Node.js project as it is maintained independently. With every release of Node.js, this project has to be updated so that internal changes of Node.js can be mapped with new abstractions.

💡 You can find the source of this project from the official GitHub repository.

Due to these factors, new Native Addons are recommended to be developed with the official abstraction API like the N-API or node-addon-api module.

3. Using the N-API

The N-API is just like the NAN project but it is maintained as a part of the Node.js project itself. Hence, we do not need to install external dependencies to import the header files. It provides a much reliable abstraction layer.

The N-API exposes node_api.h header file which contains API that abstracts internal implementation of Node.js and its libraries. With every release of Node.js, the N-API is optimized to guarantee ABI stability.

💡 These header files and other indirect dependencies are located inside /include/node/ directory of your Node.js installation directory.

Hence, when we write a Native Addon, it is guaranteed to compile against with all new versions of Node.js as all the heavy lifting of maintaining the compatibility is done by node_api.h.

💡 You can find API official documentation of N-API on here. You should also read this column on ABI Stability of N-API.

This API is written in C language which can be used in both C and C++ programs. However, the C++ API can be much easier to work with. Hence, this project maintains another node-addon-api project.

4. Using node-addon-api module

node-addon-api is an NPM module that provides napi.h header file. This header file contains C++ wrapper classes for N-API and it is officially maintained along with the N-API.

💡 You can find the source of this module from the official GitHub repository.

We are going to work with node-addon-api or napi.h to create a sample Native Addon for the demo purposes.

💡 Since C++ API is much easier to work with, you should consider node-addon-api for developing Native Addons in C++.