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.

Mastering Buffers In Node JS
NodeJS

Mastering Buffers In Node JS

As someone who is just getting started with Node JS, hearing about buffers might be a bit confusing at first. What are they exactly, and why should you care about them? I know I was pretty perplexed by this concept when I first started working with Node JS. However, once I understood what buffers were […]

Working With HTTP/2 (Web Sockets) In Node JS
NodeJS

Working With HTTP/2 (Web Sockets) In Node JS

Introduction As a web developer, I’m always on the lookout for improvements in the technology that drives our web applications. Lately, HTTP/2 and WebSockets are getting a lot of attention for their potential to enhance web browsing experiences and make web applications even faster and more dynamic. Both of these specifications are a departure from […]

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 […]

How To Cluster In Node JS
NodeJS

How To Cluster In Node JS

As a developer, I’ve always been interested in exploring different ways to improve the performance of my Node JS applications. One tool that has proven to be immensely beneficial is cluster module. In this article, we’ll dive deep into clustering for Node JS, how it works, how to implement it, and the benefits it provides. […]

Using Crypto In Node JS
NodeJS

Using Crypto In Node JS

Have you ever wondered how some of the most secure websites and applications keep your data safe from malicious attacks? Well, one of the answers lies in the use of cryptography! Cryptography is the art of writing or solving codes and ciphers, and it has been around for centuries. In the world of computer science, […]