Sling Academy
Home/Node.js/How to Write C/C++ Addons for Node.js

How to Write C/C++ Addons for Node.js

Last updated: December 28, 2023

Overview

Node.js is a powerful platform for building network applications. It is written in C, C++, and JavaScript, providing a versatile environment for developers. While JavaScript is the core language for writing Node.js modules, there are scenarios where the performance and capabilities of C/C++ are desired or necessary. Luckily, Node.js allows developers to write addons in C/C++ that can be invoked from JavaScript code. This tutorial will guide you through the process of writing C/C++ addons for Node.js.

Setting Up The Environment

To begin, you will need to set up the proper development environment, which includes installing Node.js, the Node.js package manager (npm), and node-gyp, a tool used for compiling native addon modules for Node.js.

$ sudo apt-get install nodejs
$ sudo apt-get install npm
$ npm install -g node-gyp

Creating Your First Addon

Let’s start with a simple “Hello, World!” addon. First, you’ll need to initialize a new Node.js module:

$ mkdir hello-addon
$ cd hello-addon
$ npm init -y
$ npm install node-addon-api

Create a `binding.gyp` file next to your `package.json`, with the following contents:

{
  "targets": [
    {
      "target_name": "hello",
      "sources": ["hello.cc"]
    }
  ]
}

Now, write your C++ addon in the `hello.cc` file:

#include 

Napi::String Hello(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  return Napi::String::New(env, "Hello, World!");
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set("hello", Napi::Function::New(env, Hello));
  return exports;
}

NODE_API_MODULE(hello, Init)

After writing the C++ code, compile your addon using node-gyp:

$ node-gyp configure
$ node-gyp build

Loading and Using Your Addon from JavaScript

To use your newly created addon, you’ll need to load it in a JavaScript file. Create an `index.js` with the following code:

const addon = require('./build/Release/hello');
console.log(addon.hello()); // outputs "Hello, World!"

Run your JavaScript file using Node.js to see the output:

$ node index.js

Advanced Usage

After mastering the basics, you can move on to more complex tasks, like passing arguments to your C/C++ functions, handling callbacks, and working with various data types. The possibilities are almost endless, and your addons can become as sophisticated as needed.

Here’s an example of an addon that accepts arguments and performs calculations:

#include 

Napi::Number Add(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  if (info.Length() < 2) {
    Napi::TypeError::New(env, "Two arguments expected").ThrowAsJavaScriptException();
  }

  double arg0 = info[0].As().DoubleValue();
  double arg1 = info[1].As().DoubleValue();
  double sum = arg0 + arg1;

  return Napi::Number::New(env, sum);
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set("add", Napi::Function::New(env, Add));
  return exports;
}

NODE_API_MODULE(addon, Init)

And in your JavaScript file:

const addon = require('./build/Release/addon');
console.log('The sum is', addon.add(3, 5)); // outputs "The sum is 8"

Working with Asynchronous Code

In a Node.js environment, non-blocking operations are crucial. You can write asynchronous C/C++ addons that leverage Node.js’s event loop. For example, using the N-API’s thread-safe function:

#include <napi.h>
#include <thread>

// A data structure to hold the data to be passed to the JavaScript function
struct Data {
  int value;
  Napi::ThreadSafeFunction tsfn;
};

// A callback function to receive the data from the native thread
void Callback(Napi::Env env, Napi::Function jsCallback, Data* data) {
  // Convert the data to a JS value and pass it to the JS callback
  jsCallback.Call({ Napi::Number::New(env, data->value) });

  // Release the thread-safe function
  data->tsfn.Release();
}

// A function to run on the native thread
void ThreadFunction(Data* data) {
  // Do some work with the data
  data->value *= 2;

  // Call the thread-safe function with the data
  data->tsfn.NonBlockingCall(data, Callback);
}

// A function exposed to JS to create a thread-safe function
Napi::Value CreateThreadSafeFunction(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();

  // Get the JS callback function as the first argument
  Napi::Function jsCallback = info[0].As<Napi::Function>();

  // Create a thread-safe function with the JS callback
  Napi::ThreadSafeFunction tsfn = Napi::ThreadSafeFunction::New(
    env,
    jsCallback,
    "TestResource",
    0, // unlimited queue
    1 // initial thread count
  );

  // Create a data structure with an initial value and the tsfn
  Data* data = new Data{ 42, tsfn };

  // Acquire the tsfn before passing it to the thread
  tsfn.Acquire();

  // Start a new thread with the data
  std::thread thread(ThreadFunction, data);

  // Detach the thread
  thread.detach();

  // Return the tsfn as a JS value
  return tsfn.Value();
}

// Initialize the addon
Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set("createThreadSafeFunction", Napi::Function::New(env, CreateThreadSafeFunction));
  return exports;
}

// Register the addon
NODE_API_MODULE(addon, Init)

A thread-safe function is a way to invoke a JavaScript function from a native thread without blocking the event loop. It allows you to pass data from the native thread to the JavaScript function as an argument. You can create a thread-safe function using the Napi::ThreadSafeFunction::New method, and then call it from the native thread using the Napi::ThreadSafeFunction::NonBlockingCall method.

Debugging and Profiling

Debugging native code can be a little trickier than JavaScript. Tools like gdb and Valgrind can be used to debug and profile your C/C++ Node.js addons.

Conclusion

Writing C/C++ addons for Node.js opens up a plethora of possibilities for performance optimization and integration with existing C/C++ libraries. By following this tutorial, you’re now equipped with the knowledge to extend the capabilities of your Node.js applications with powerful native addons.

Next Article: Should you use Docker or PM2 to run Node.js apps in production

Previous Article: How to Setup a Node.js Cluster for Speed & Stability

Series: Node.js Intermediate Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates