C++ Addons In Node JS

Introduction:


As a software developer, I am always looking for ways to improve the performance of my applications. One way to achieve this is by using C++ Addons in Node JS. In this article, we will explore what C++ Addons are, why they are useful in Node JS, and how to create and use them.

Getting Started:


Before we dive into C++ Addons, let’s first make sure we have all the necessary tools installed. First, let’s install Node JS, which can be found on the official Node JS website. Next, we will need to install node-gyp, which is a tool that is used to build Node.js native modules. You can install it using the following command:

npm install -g node-gyp

Now that we have our tools installed, let’s take a look at what C++ Addons are.

What are C++ Addons?


C++ Addons are libraries written in C++ that can be loaded into a Node JS application. They allow you to write performance-critical code in C++ and call it from Node JS. This is useful when you need to perform tasks that are computationally expensive, such as image processing, cryptography, or machine learning.

Why are C++ Addons useful in Node JS?


Node JS is a powerful and flexible platform for building web applications. However, it is built on top of the V8 JavaScript engine, which is designed for single-threaded, event-driven applications. While Node JS is excellent at handling I/O operations, it can struggle with CPU-bound tasks. This is where C++ Addons come in. By writing performance-critical code in C++, we can take advantage of multiple threads and optimize the use of system resources.

How to create a basic C++ Addon:


Now that we understand the basics of C++ Addons, let’s create a basic example. First, create a new directory and navigate into it from your terminal or command prompt. Next, create a file called addon.cc. This is where we will write our C++ code.

#include <node.h>

namespace demo {

  // demo::Method is a struct that holds a callback function
  struct Method {
    v8::Persistent<v8::Function> callback;
    int val;
  };

  // This is our method that we want to expose to Node JS
  void Add(const v8::FunctionCallbackInfo<v8::Value>& args) {
    v8::Isolate* isolate = args.GetIsolate();

    // Check the number of arguments passed
    if (args.Length() < 2) {
      isolate->ThrowException(v8::Exception::TypeError(
          v8::String::NewFromUtf8(isolate, "Wrong number of arguments")));
      return;
    }

    // Check the argument data types
    if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
      isolate->ThrowException(v8::Exception::TypeError(
          v8::String::NewFromUtf8(isolate, "Wrong arguments")));
      return;
    }

    // Perform the addition
    double arg0 = args[0]->NumberValue();
    double arg1 = args[1]->NumberValue();
    double sum = arg0 + arg1;

    // Convert the sum to a V8 result
    v8::Local<v8::Number> result = v8::Number::New(isolate, sum);

    // Return the result to the callback function
    args.GetReturnValue().Set(result);
  }

  // This is our initialization function that will expose our method to Node JS
  void Init(v8::Local<v8::Object> exports) {
    NODE_SET_METHOD(exports, "add", Add);
  }

  NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

}  // namespace demo

This example defines a single method called Add that takes two arguments and adds them together. We also define an initialization function that exposes the Add method to Node JS.

To build and use this addon, we need to create a binding.gyp file in the same directory as addon.cc. This file tells node-gyp how to build our addon.

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

Next, run the following command to build the addon:

node-gyp configure build

This will create a build directory with our compiled addon. To use it in a Node JS application, create a new file called app.js and require our addon:

const addon = require('./build/Release/addon');

console.log('Result:', addon.add(1, 2));

Run the app.js file with:

node app.js

You should see the result logged to the console: “Result: 3”.

Advanced Concepts:


Now that we have a basic understanding of how to create a C++ Addon, let’s take a look at some more advanced concepts.

Asynchronous C++ Addons:


When working with Node JS, it is important to keep in mind the event loop. Node JS is designed to be single-threaded, and so blocking the event loop with long-running synchronous operations is not recommended. For performance-intensive tasks that cannot be broken down into smaller chunks, it is best to use an Asynchronous C++ Addon.

An Asynchronous C++ Addon uses a background thread to perform the task and then sends the result back to Node JS. Here is an example of how to create an Asynchronous C++ Addon:

#include <node.h>
#include <uv.h>

namespace demo {

  // demo::Baton is a struct that holds data to be passed between threads
  struct Baton {
    uv_work_t req;
    v8::Persistent<v8::Function> callback;
    double arg1;
    double arg2;
    double result;
  };

  // This is our method that we want to expose to Node JS
  void AddAsync(const v8::FunctionCallbackInfo<v8::Value>& args) {
    v8::Isolate* isolate = args.GetIsolate();

    // Check the number of arguments passed
    if (args.Length() < 3) {
      isolate->ThrowException(v8::Exception::TypeError(
          v8::String::NewFromUtf8(isolate, "Wrong number of arguments")));
      return;
    }

    // Check the argument data types
    if (!args[0]->IsNumber() || !args[1]->IsNumber() || !args[2]->IsFunction()) {
      isolate->ThrowException(v8::Exception::TypeError(
          v8::String::NewFromUtf8(isolate, "Wrong argument types")));
      return;
    }

    // Create a new Baton object
    Baton* baton = new Baton();
    baton->req.data = baton;
    baton->callback.Reset(isolate, v8::Local<v8::Function>::Cast(args[2]));
    baton->arg1 = args[0]->NumberValue();
    baton->arg2 = args[1]->NumberValue();

    // Schedule the work on the background thread
    uv_queue_work(uv_default_loop(), &baton->req, [](uv_work_t* req) {
      Baton* baton = static_cast<Baton*>(req->data);

      // Perform the addition in the background thread
      baton->result = baton->arg1 + baton->arg2;
    }, [](uv_work_t* req, int status) {
      v8::Isolate* isolate = v8::Isolate::GetCurrent();
      v8::HandleScope handleScope(isolate);

      Baton* baton = static_cast<Baton*>(req->data);

      // Pass the result back to the callback function
      v8::Local<v8::Value> argv[] = {
        v8::Number::New(isolate, baton->result)
      };
      v8::Local<v8::Function> callback = v8::Local<v8::Function>::New(isolate, baton->callback);
      callback->Call(isolate->GetCurrentContext()->Global(), 1, argv);

      // Clean up the Baton object
      baton->callback.Reset();
      delete baton;
    });

    args.GetReturnValue().SetUndefined();
  }

  // This is our initialization function that will expose our method to Node JS
  void Init(v8::Local<v8::Object> exports) {
    NODE_SET_METHOD(exports, "addAsync", AddAsync);
  }

  NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

}  // namespace demo

In this example, we define a new method called AddAsync that takes three arguments: two numbers and a callback function. We create a new Baton object that holds the data to be passed between threads. We then use the uv_queue_work function to schedule the work on the background thread.

When the work is complete, we pass the result back to the callback function and clean up the Baton object. To use this addon, we can call the AddAsync method in our Node JS application with the same syntax as before.

Code Example:


Here is an example of a practical application that uses a C++ Addon in Node JS. Suppose we have a web application that needs to perform real-time image processing on user-uploaded images. We can use a C++ Addon to perform the image processing, which is much faster than performing the same operation in JavaScript.

#include <node.h>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

namespace demo {

  // demo::Baton is a struct that holds data to be passed between threads
  struct Baton {
    uv_work_t req;
    v8::Persistent<v8::Function> callback;
    std::string imagePath;
    cv::Scalar color;
    int thickness;
  };

  // This is our method that we want to expose to Node JS
  void DrawRectangleAsync(const v8::FunctionCallbackInfo<v8::Value>& args) {
    v8::Isolate* isolate = args.GetIsolate();

    // Check the number of arguments passed
    if (args.Length() < 4) {
      isolate->ThrowException(v8::Exception::TypeError(
          v8::String::NewFromUtf8(isolate, "Wrong number of arguments")));
      return;
    }

    // Check the argument data types
    if (!args[0]->IsString() || !args[1]->IsArray() || !args[2]->IsNumber() || !args[3]->IsFunction()) {
      isolate->ThrowException(v8::Exception::TypeError(
          v8::String::NewFromUtf8(isolate, "Wrong argument types")));
      return;
    }

    // Convert the arguments to the appropriate types
    std::string imagePath = std::string(*v8::String::Utf8Value(args[0]));
    v8::Local<v8::Array> colorArray = v8::Local<v8::Array>::Cast(args[1]);
    cv::Scalar color(colorArray->Get(0)->NumberValue(), colorArray->Get(1)->NumberValue(), colorArray->Get(2)->NumberValue(), colorArray->Get(3)->NumberValue());
    int thickness = args[2]->NumberValue();

    // Create a new Baton object
    Baton* baton = new Baton();
    baton->req.data = baton;
    baton->callback.Reset(isolate, v8::Local<v8::Function>::Cast(args[3]));
    baton->imagePath = imagePath;
    baton->color = color;
    baton->thickness = thickness;

    // Schedule the work on the background thread
    uv_queue_work(uv_default_loop(), &baton->req, [](uv_work_t* req) {
      Baton* baton = static_cast<Baton*>(req->data);

      // Read the image from the file
      cv::Mat image = cv::imread(baton->imagePath, CV_LOAD_IMAGE_COLOR);

      // Draw the rectangle on the image
      cv::rectangle(image, cv::Point(50, 50), cv::Point(100, 100), baton->color, baton->thickness);

      // Write the image back to the file
      cv::imwrite(baton->imagePath, image);
    }, [](uv_work_t* req, int status) {
      v8::Isolate* isolate = v8::Isolate::GetCurrent();
      v8::HandleScope handleScope(isolate);

      Baton* baton = static_cast<Baton*>(req->data);

      // Pass the result back to the callback function
      v8::Local<v8::Value> argv[] = {
        v8::Undefined(isolate)
      };
      v8::Local<v8::Function> callback = v8::Local<v8::Function>::New(isolate, baton->callback);
      callback->Call(isolate->GetCurrentContext()->Global(), 1, argv);

      // Clean up the Baton object
      baton->callback.Reset();
      delete baton;
    });

    args.GetReturnValue().SetUndefined();
  }

  // This is our initialization function that will expose our method to Node JS
  void Init(v8::Local<v8::Object> exports) {
    NODE_SET_METHOD(exports, "drawRectangleAsync", DrawRectangleAsync);
  }

  NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

}  // namespace demo

In this example, we define a new method called DrawRectangleAsync that takes four arguments: an image path, a color array, a thickness value, and a callback function. We create a new Baton object that holds the data to be passed between threads. We then use the uv_queue_work function to schedule the work on the background thread.

When the work is complete, we pass the result back to the callback function and clean up the Baton object. To use this addon, we can call the DrawRectangleAsync method in our Node JS application with the same syntax as before, providing the appropriate arguments.

Conclusion:


C++ addons in Node JS provide a way to extend Node JS with high-performance C++ code. By using addons, we can take advantage of the speed and power of C++ while still using the familiar Node JS syntax. In this article, we covered the basics of creating a C++ addon in Node JS and provided examples of practical applications. With the knowledge gained in this article, readers should be able to further explore the use of C++ addons in Node JS to create high-performance applications.

Events In Node JS
NodeJS

Events In Node JS

Introduction As a Node JS developer, I have often found myself perplexed by events in Node JS. At first, I thought that events were just functions that get triggered when something happens. However, as I delved deeper into this topic, I realized that events are much more powerful and efficient than I had originally thought. […]

C++ Embedder API With Node JS
NodeJS

C++ Embedder API With Node JS

Introduction: As a programmer, I have always been fascinated with the power of NodeJS. It is a popular JavaScript runtime that can be used for server-side scripting. The beauty of NodeJS is that it allows for easy handling of I/O operations. However, sometimes the complexities of a project may go beyond just JavaScript coding, and […]

Asynchronous Context Tracking With Node JS
NodeJS

Asynchronous Context Tracking With Node JS

As someone who has spent a lot of time working with Node JS, I have come to understand the importance of Asynchronous Context Tracking in the development of high-performance applications. In this article, I will explore the concept of Asynchronous Context Tracking with Node JS, its advantages, techniques, challenges and best practices. Before we dive […]

Working With HTTPS In Node JS
NodeJS

Working With HTTPS In Node JS

As a developer, I’m always on the lookout for security protocols that can help me protect users’ sensitive information. HTTP, while functional, lacks the encryption necessary to truly secure data transmission over the web. This is where HTTPS comes in. But working with HTTPS can be a bit daunting, especially if you’re new to it. […]

What Is Node JS: A Comprehensive Guide
NodeJS

What Is Node JS: A Comprehensive Guide

Introduction As a full-stack developer, I have been working with various technologies over the past few years. But one technology that has caught my attention recently is NodeJS. With its event-driven and non-blocking I/O model, NodeJS has become an excellent choice for building real-time and highly-scalable applications. So, what is NodeJS, and how does it […]

Command Line Options In Node JS
NodeJS

Command Line Options In Node JS

As a developer who loves working with Node JS, I’ve found myself using command line options in many of my projects. Command line options can make our Node JS programs more user-friendly and powerful. In this article, I’ll explain what command line options are, how to parse them in Node JS, how to use them […]