addons

C++ Addons in Node.js

Addons are extra modules written in C++ that can extend the capabilities of Node.js. They allow you to connect Node.js with other C/C++ libraries.

How Addons Work

There are a few ways to create addons, but the most common is using Node-API. Node-API provides a set of functions that make it easy to write addons without having to deal directly with the underlying V8 and libuv libraries.

V8

V8 is the engine that powers Node.js and executes JavaScript code. It's like the brain that understands and runs the code you write in JavaScript.

libuv

libuv is a cross-platform library that provides Node.js with low-level functionality, such as creating threads, starting timers, and handling file operations. It's like the toolkit that helps Node.js connect to the operating system and manage system resources.

Internal Node.js Libraries

Node.js also includes its own set of libraries that addons can use. One important one is node::ObjectWrap, which allows addons to create their own JavaScript objects.

Real-World Applications

Addons are used in a wide variety of applications, including:

  • Image processing: Addons can provide faster performance for image manipulation tasks.

  • Data analysis: Addons can be used to analyze large datasets quickly and efficiently.

  • Hardware integration: Addons can connect Node.js to hardware devices, such as microcontrollers or sensors.

Code Example

Here's a simple example of an addon that prints a message:

#include <node_api.h>

// The initialization function, called when the addon is loaded.
napi_value Init(napi_env env, napi_value exports) {
  napi_status status;

  // Create a new JavaScript string object.
  napi_value message;
  status = napi_create_string_utf8(env, "Hello, world!", NAPI_AUTO_LENGTH, &message);
  if (status != napi_ok) {
    napi_throw_error(env, "ERR_FAILED_TO_CREATE_STRING", "Could not create JavaScript string.");
    return nullptr;
  }

  // Set the exported property of the module to the message object.
  status = napi_set_named_property(env, exports, "message", message);
  if (status != napi_ok) {
    napi_throw_error(env, "ERR_FAILED_TO_SET_PROPERTY", "Could not set exported property.");
    return nullptr;
  }

  return exports;
}

// Register the addon with Node.js.
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

This addon defines a function that creates a JavaScript string object with the message "Hello, world!". It then exports the message object as a property of the module. When you load this addon in a Node.js script, you can access the message property to print the message.

Conclusion

Addons are a powerful way to extend the capabilities of Node.js and integrate with C/C++ libraries. They are used in a wide variety of real-world applications, from image processing to data analysis.


Node.js Addons

In Node.js, you can extend the functionality of JavaScript by creating and using addons. Addons are written in C++ and provide access to native system resources or perform complex calculations more efficiently.

Creating a Hello World Addon

Let's create a simple "hello world" addon:

// hello.cc
#include <node.h>

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

// Function that returns "world"
void Method(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(
      isolate, "world").ToLocalChecked());
}

// Initializes the addon by registering the "hello" method
void Initialize(Local<Object> exports) {
  NODE_SET_METHOD(exports, "hello", Method);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

}  // namespace demo

Understanding the Code

  • Initialize Function: This is the entry point of the addon. It exports the "hello" method to Node.js.

  • Method Function: This function handles the "hello" method. It returns "world" as a string.

  • NODE_MODULE Macro: Registers the addon with Node.js. The NODE_GYP_MODULE_NAME macro specifies the name of the final addon binary.

Building and Using the Addon

To build and use the addon:

$ node-gyp configure
$ node-gyp build
$ node example.js

Example.js:

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

console.log(addon.hello()); // Output: "world"

Potential Applications

Addons are used in a wide variety of applications, including:

  • Interfacing with hardware devices

  • Performing computationally intensive tasks

  • Accessing low-level system information

  • Integrating with native libraries written in other languages


Context-aware addons

In certain environments, Node.js addons may need to be loaded multiple times in different contexts. For example, the Electron runtime runs multiple instances of Node.js in a single process. Each instance will have its own require() cache, meaning each instance will need a native addon to behave correctly when loaded via require(). This means that the addon must support multiple initializations.

Creating a context-aware addon

A context-aware addon can be created using the NODE_MODULE_INITIALIZER macro, which expands to the name of a function that Node.js will expect to find when it loads an addon. An addon can be initialized as follows:

using namespace v8;

extern "C" NODE_MODULE_EXPORT void
NODE_MODULE_INITIALIZER(Local<Object> exports,
                        Local<Value> module,
                        Local<Context> context) {
  /* Perform addon initialization steps here. */
}

Another option is to use the NODE_MODULE_INIT() macro, which will also construct a context-aware addon. Unlike NODE_MODULE(), which is used to construct an addon around a given addon initializer function, NODE_MODULE_INIT() serves as the declaration of such an initializer to be followed by a function body.

The following three variables may be used inside the function body following an invocation of NODE_MODULE_INIT():

  • Local<Object> exports,

  • Local<Value> module, and

  • Local<Context> context

Managing global static data

Building a context-aware addon requires careful management of global static data. Since the addon may be loaded multiple times, potentially even from different threads, any global static data stored in the addon must be properly protected and must not contain any persistent references to JavaScript objects. This is because JavaScript objects are only valid in one context and will likely cause a crash when accessed from the wrong context or from a different thread than the one on which they were created.

Avoiding global static data

A context-aware addon can be structured to avoid global static data by performing the following steps:

  1. Define a class which will hold per-addon-instance data and which has a static member of the form:

static void DeleteInstance(void* data) {
  // Cast `data` to an instance of the class and delete it.
}
  1. Heap-allocate an instance of this class in the addon initializer. This can be accomplished using the new keyword.

  2. Call node::AddEnvironmentCleanupHook(), passing it the above-created instance and a pointer to DeleteInstance(). This will ensure the instance is deleted when the environment is torn down.

  3. Store the instance of the class in a v8::External, and

  4. Pass the v8::External to all methods exposed to JavaScript by passing it to v8::FunctionTemplate::New() or v8::Function::New() which creates the native-backed JavaScript functions. The third parameter of v8::FunctionTemplate::New() or v8::Function::New() accepts the v8::External and makes it available in the native callback using the v8::FunctionCallbackInfo::Data() method.

This will ensure that the per-addon-instance data reaches each binding that can be called from JavaScript. The per-addon-instance data must also be passed into any asynchronous callbacks the addon may create.

Example of a context-aware addon

The following example illustrates the implementation of a context-aware addon:

#include <node.h>

using namespace v8;

class AddonData {
 public:
  explicit AddonData(Isolate* isolate):
      call_count(0) {
    // Ensure this per-addon-instance data is deleted at environment cleanup.
    node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this);
  }

  // Per-addon data.
  int call_count;

  static void DeleteInstance(void* data) {
    delete static_cast<AddonData*>(data);
  }
};

static void Method(const v8::FunctionCallbackInfo<v8::Value>& info) {
  // Retrieve the per-addon-instance data.
  AddonData* data =
      reinterpret_cast<AddonData*>(info.Data().As<External>()->Value());
  data->call_count++;
  info.GetReturnValue().Set((double)data->call_count);
}

// Initialize this addon to be context-aware.
NODE_MODULE_INIT(/* exports, module, context */) {
  Isolate* isolate = context->GetIsolate();

  // Create a new instance of `AddonData` for this instance of the addon and
  // tie its life cycle to that of the Node.js environment.
  AddonData* data = new AddonData(isolate);

  // Wrap the data in a `v8::External` so we can pass it to the method we
  // expose.
  Local<External> external = External::New(isolate, data);

  // Expose the method `Method` to JavaScript, and make sure it receives the
  // per-addon-instance data we created above by passing `external` as the
  // third parameter to the `FunctionTemplate` constructor.
  exports->Set(context,
               String::NewFromUtf8(isolate, "method").ToLocalChecked(),
               FunctionTemplate::New(isolate, Method, external)
                  ->GetFunction(context).ToLocalChecked()).FromJust();
}

Real-world applications of context-aware addons

Context-aware addons can be used in any situation where multiple instances of Node.js are running in the same process and need to share data or functionality. For example, context-aware addons can be used in:

  • Electron apps, where multiple instances of Node.js are running in different renderer processes.

  • Web extensions, where multiple instances of Node.js are running in different browser tabs.

  • Microservices, where multiple instances of Node.js are running in different containers or on different servers.


Worker Support

To be used in different Node.js environments like the main thread and a worker thread, an add-on needs to be either:

  1. A Node-API addon

  2. Declared as context-aware using NODE_MODULE_INIT()

To support worker threads, add-ons must clean up any assigned resources when a worker thread exists. This is possible using the AddEnvironmentCleanupHook() function:

void AddEnvironmentCleanupHook(v8::Isolate* isolate, void (*fun)(void* arg), void* arg);

This function includes a hook that will operate before a given Node.js instance shuts down. If essential, these hooks can be eliminated before they are run using RemoveEnvironmentCleanupHook(), which has the same arrangement. Callbacks are operated in the last-in first-out order.

If necessary, there are additional AddEnvironmentCleanupHook() and RemoveEnvironmentCleanupHook() overloads, where the cleanup hook requires a callback function. This can be applied for shutting down asynchronous sources, like any libuv handles enrolled by the addon.

The accompanying addon.cc uses AddEnvironmentCleanupHook:

// addon.cc
#include <node.h>
#include <assert.h>
#include <stdlib.h>

using node::AddEnvironmentCleanupHook;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Object;

// Note: In a real-world application, do not rely on static/global data.
static char cookie[] = "yum yum";
static int cleanup_cb1_called = 0;
static int cleanup_cb2_called = 0;

static void cleanup_cb1(void* arg) {
  Isolate* isolate = static_cast<Isolate*>(arg);
  HandleScope scope(isolate);
  Local<Object> obj = Object::New(isolate);
  assert(!obj.IsEmpty());  // assert VM is still alive
  assert(obj->IsObject());
  cleanup_cb1_called++;
}

static void cleanup_cb2(void* arg) {
  assert(arg == static_cast<void*>(cookie));
  cleanup_cb2_called++;
}

static void sanity_check(void*) {
  assert(cleanup_cb1_called == 1);
  assert(cleanup_cb2_called == 1);
}

// Initialize this addon to be context-aware.
NODE_MODULE_INIT(/* exports, module, context */) {
  Isolate* isolate = context->GetIsolate();

  AddEnvironmentCleanupHook(isolate, sanity_check, nullptr);
  AddEnvironmentCleanupHook(isolate, cleanup_cb2, cookie);
  AddEnvironmentCleanupHook(isolate, cleanup_cb1, isolate);
}

Test in JavaScript by running:

// test.js
require("./build/Release/addon");

Real-world Applications

  • Creating add-ons that can be used in various Node.js environments, such as main threads and worker threads.

  • Cleaning up resources allocated by add-ons when a worker thread exits to prevent resource leaks and crashes.


Building Node.js Addons

Step 1: Describe Build Configuration

Create a file called binding.gyp in your project directory. This file describes how to build your addon.

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

This file tells the build tool (node-gyp) about your addon's source files (hello.cc).

Step 2: Generate Build Files

Use the node-gyp configure command to create the actual build files (Makefile on Unix, vcxproj on Windows).

node-gyp configure

Step 3: Build the Addon

Use the node-gyp build command to compile your addon into a addon.node file.

node-gyp build

Step 4: Use the Addon in Node.js

Load the compiled addon into your Node.js script using require().

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

The addon's functions can now be used as shown in the example below.

console.log(addon.hello()); // Prints "world"

Using the bindings Package

The bindings package simplifies the process of loading compiled addons by handling the search for the correct addon file based on the platform and environment.

const bindings = require("bindings");
const addon = bindings("addon");

This version provides more flexibility and is more robust than the manual require() approach.

Real-World Applications

Node.js addons can be used to extend the capabilities of Node.js by adding native code functionality. Some examples include:

  • Interfacing with hardware devices (e.g., sensors, actuators)

  • High-performance computations

  • Accessing operating system-specific features


Linking to Libraries Included with Node.js

Introduction:

Node.js uses libraries like V8 (JavaScript engine), libuv (event loop), and OpenSSL (encryption) that are built into Node.js. When creating add-ons (extensions) for Node.js, you need to link to these libraries so your add-ons can use their functionality.

Locating Headers Automatically:

node-gyp is a tool that helps build add-ons for Node.js. When you use node-gyp, it automatically finds the headers (like #include <v8.h>) you need to include in your add-on.

Downloading Headers vs. Full Source:

When building your add-on, node-gyp will download either the full source code for the specific version of Node.js you're using or just the headers.

  • Full Source: You can access all the dependencies of Node.js.

  • Headers Only: You can access only the symbols exported by Node.js.

Using the --nodedir Flag:

You can specify the location of the Node.js source code using the --nodedir flag when running node-gyp. This gives your add-on access to the full source code.

Real-World Examples:

Add-ons can be used to extend Node.js's functionality in various ways:

  • Image Processing: Create add-ons that use image manipulation libraries.

  • Database Connectivity: Enhance Node.js with add-ons that connect to databases.

  • Networking Utilities: Develop add-ons that provide additional networking capabilities.

Code Implementation:

To link to V8 in your add-on, you would typically include this header:

#include <v8.h>

Potential Applications:

Add-ons can be used in a wide range of applications, including:

  • Web Servers: Enhance performance or add custom features to web servers.

  • Data Analysis: Process large datasets or create custom analytics tools.

  • Machine Learning: Implement machine learning algorithms or integrate with machine learning libraries.

  • Internet of Things (IoT): Connect and interact with IoT devices from Node.js applications.


Loading Addons Using require()

What are Addons?

Addons are extra pieces of code that you can add to Node.js to do things that Node.js can't do on its own. For example, you could use an addon to control a robot or to work with a specific type of hardware.

How to Load Addons

To load an addon, you use the require() function. The require() function tells Node.js to find the addon file and load it into your program.

Filename Extension for Addons

The addon files have a special filename extension: .node. This tells Node.js that the file contains an addon.

Omitting the .node Extension

Usually, you can omit the .node extension when using require() to load an addon. Node.js will still find and load the addon.

Potential Application

One potential application of addons is to control hardware devices. For example, you could use an addon to control a robot or to turn on and off a light.

Complete Code Example

// Load the addon
const addon = require("./my-addon.node");

// Use the addon
addon.sayHello(); // Prints "Hello, world!"

Plain English Explanation

Imagine you have a box of tools. The box of tools is like Node.js, and the tools inside the box are like addons. You can add different tools to the box to do different things.

To use a tool, you need to find it and take it out of the box. To do this, you use a function called require(). The require() function tells Node.js to go find the tool and bring it to you.

The tools in the box have a special name tag: .node. This tells Node.js that the tool is an addon.

Usually, you don't have to remember the name tag when using require(). Node.js will still find the tool even if you forget to include the name tag.

One thing you can do with these tools is control different gadgets. For example, you could use a tool to turn on a light or to move a robot.


What are Native Abstractions for Node.js (nan)?

In Node.js, you can use native code written in C++ to create faster and more efficient extensions to the JavaScript environment. However, the V8 engine that powers Node.js can change dramatically between versions.

To help ensure that your native extensions keep working even with V8 updates, the Native Abstractions for Node.js (nan) library provides a stable set of APIs that hide the details of the underlying V8 changes.

How to use nan

To use nan, you need to include the following header file in your C++ code:

#include <nan.h>

Let's take a simple example of how to use nan to create a custom function that you can call from JavaScript:

#include <nan.h>

NAN_METHOD(Add) {
  auto info = info.GetReturnValue().FromNumber(1 + 2);
}

NAN_MODULE_INIT(Init) {
  Nan::Set(target, Nan::New("add").ToLocalChecked(),
           Nan::GetFunction(Nan::New<FunctionCallback>(Add)).ToLocalChecked());
}

NODE_MODULE(addon, Init)

This code defines a function named Add that adds two numbers together. It then exports this function as a JavaScript function that you can call from Node.js code.

To use this extension in your Node.js code, you would do the following:

const addon = require("addon");

console.log(addon.add(1, 2)); // 3

Real-world applications of nan

Nan is used in many popular Node.js extensions, including:

  • bcrypt - For hashing passwords securely

  • sharp - For image processing

  • FFmpeg - For video processing

Benefits of using nan

Using nan provides several benefits:

  • Stability: Your native extensions will continue to work even when V8 changes.

  • Portability: You can write code that works across different platforms and versions of Node.js.

  • Simplicity: Nan provides a simple and easy-to-use API for creating native extensions.


What is Node.js?

Node.js is a JavaScript runtime environment that allows you to write server-side code using JavaScript. It's used to create web applications, web servers, and other types of applications.

What are Native Addons?

Native addons are modules written in a language other than JavaScript, such as C or C++, that can be used in Node.js applications. They allow you to access native features of the operating system that are not available through JavaScript.

What is Node-API?

Node-API is an Application Binary Interface (ABI) that provides a stable set of functions that can be used to create native addons. This means that native addons compiled for one version of Node.js will work on subsequent versions of Node.js without recompilation.

How to Use Node-API

To use Node-API, you need to include the node_api.h header file in your native addon code. You can then use the functions provided by Node-API to access native features of the operating system.

Example:

The following code snippet shows how to create a simple native addon using Node-API:

#include <node_api.h>

napi_value hello_world(napi_env env, napi_callback_info args) {
  napi_value greeting;
  napi_create_string_utf8(env, "Hello, world!", NAPI_AUTO_LENGTH, &greeting);
  return greeting;
}

napi_value init(napi_env env, napi_value exports) {
  napi_value fn;
  napi_create_function(env, nullptr, 0, hello_world, nullptr, &fn);
  napi_set_named_property(env, exports, "hello", fn);
  return exports;
}

NAPI_MODULE(hello, init)

This addon can be compiled and used in a Node.js application using the following steps:

  1. Install the node-gyp package using the following command:

    npm install -g node-gyp
  2. Create a package.json file for your addon with the following contents:

    {
      "name": "hello",
      "version": "1.0.0",
      "description": "A simple Node.js addon using Node-API",
      "author": "Your Name",
      "license": "MIT",
      "binary": {
        "module_name": "hello",
        "module_path": "./build/Release/hello.node"
      }
    }
  3. Create a binding.gyp file for your addon with the following contents:

    {
      "targets": [
        {
          "target_name": "hello",
          "sources": [
            "hello.cc"
          ],
          "include_dirs": [
            "/usr/local/include/node"
          ],
          "libraries": [
            "-lstdc++"
          ]
        }
      ]
    }
  4. Build the addon using the following command:

    node-gyp configure build
  5. Install the addon in your Node.js application using the following command:

    npm install ./

You can now use the hello addon in your Node.js application as follows:

const hello = require("hello");

hello.hello(); // Outputs "Hello, world!"

Real-World Applications

Native addons can be used to extend the functionality of Node.js applications in many ways. Here are a few examples:

  • Accessing low-level operating system features, such as file I/O, network I/O, and hardware devices

  • Improving the performance of computationally intensive tasks

  • Integrating with legacy code written in other languages

  • Creating custom modules for specific applications

Conclusion

Node-API is a powerful tool that allows you to create native addons for Node.js applications. By using the stable ABI provided by Node-API, you can ensure that your addons will work on future versions of Node.js without recompilation.


Addon examples

Addons are a way to extend the functionality of Node.js by writing native code in C++ that can be called from JavaScript. This can be useful for tasks that are difficult or impossible to do in JavaScript, such as accessing the operating system or working with hardware.

Examples:

Example 1: Hello World

The following addon simply prints "Hello, World!" to the console:

#include <node.h>

namespace addon {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void HelloWorld(const FunctionCallbackInfo<Value>& info) {
  Isolate* isolate = info.GetIsolate();
  Local<String> result = String::NewFromUtf8(
      isolate, "Hello, World!");
  info.GetReturnValue().Set(result);
}

void Initialize(Local<Object> exports) {
  NODE_SET_METHOD(exports, "helloWorld", HelloWorld);
}

NODE_MODULE(addon, Initialize)

}  // namespace addon

To use this addon, you would first need to install it using npm:

npm install addon

Then, you can require the addon in your JavaScript code:

const addon = require("addon");

addon.helloWorld(); // Prints "Hello, World!" to the console

Example 2: Accessing the operating system

The following addon shows how to access the operating system using the child_process module:

#include <node.h>
#include <string>

namespace addon {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void GetUsername(const FunctionCallbackInfo<Value>& info) {
  Isolate* isolate = info.GetIsolate();

  std::string username = getenv("USER");
  Local<String> result = String::NewFromUtf8(
      isolate, username.c_str());
  info.GetReturnValue().Set(result);
}

void Initialize(Local<Object> exports) {
  NODE_SET_METHOD(exports, "getUsername", GetUsername);
}

NODE_MODULE(addon, Initialize)

}  // namespace addon

To use this addon, you would first need to install it using npm:

npm install addon

Then, you can require the addon in your JavaScript code:

const addon = require("addon");

addon.getUsername((username) => {
  console.log(`Your username is ${username}`);
});

Example 3: Working with hardware

The following addon shows how to work with hardware using the i2c-bus module:

#include <node.h>
#include <i2c-bus.h>

namespace addon {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void ReadI2C(const FunctionCallbackInfo<Value>& info) {
  Isolate* isolate = info.GetIsolate();

  i2c_bus bus("/dev/i2c-1");
  i2c_smbus_data data;
  int result = bus.i2c_smbus_read_byte_data(0x42, 0x00, &data);
  if (result < 0) {
    throw std::runtime_error("Failed to read from I2C bus");
  }
  Local<String> result = String::NewFromUtf8(
      isolate, std::to_string(data.byte).c_str());
  info.GetReturnValue().Set(result);
}

void Initialize(Local<Object> exports) {
  NODE_SET_METHOD(exports, "readI2C", ReadI2C);
}

NODE_MODULE(addon, Initialize)

}  // namespace addon

To use this addon, you would first need to install it using npm:

npm install addon

Then, you can require the addon in your JavaScript code:

const addon = require("addon");

addon.readI2C((value) => {
  console.log(`The value read from the I2C bus is ${value}`);
});

Potential applications in real world

Addons can be used in a wide variety of real-world applications, such as:

  • Interfacing with hardware: Addons can be used to control hardware devices, such as sensors, actuators, and motors.

  • Accessing operating system features: Addons can be used to access features of the operating system that are not available through the standard Node.js API, such as low-level file I/O and networking.

  • Improving performance: Addons can be used to improve the performance of Node.js applications by performing computationally intensive tasks in native code.

  • Extending the functionality of Node.js: Addons can be used to extend the functionality of Node.js by implementing new features that are not available in the standard library.


Function Arguments in Node.js Addons

Addons are extensions that allow you to extend the functionality of Node.js using C/C++ code. When you call a function from an addon in JavaScript, you need to pass arguments and receive the result.

Passing Arguments

When you call a function in an addon from JavaScript, the arguments are passed as an array of Value objects:

void Add(const FunctionCallbackInfo<Value>& args) {
  // args[0] is the first argument, args[1] is the second argument, etc.
}

Each element in the args array represents a value that was passed from JavaScript. You can check the type of a value using IsNumber(), IsString(), etc.

Returning a Result

To return a result from a function in an addon, you set the ReturnValue property of the FunctionCallbackInfo object:

// Set the return value to 8
args.GetReturnValue().Set(Number::New(isolate, 8));

Example

Here's a complete example of an addon that defines a function called add that takes two numbers and returns their sum:

#include <node.h>

namespace demo {

using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;

void Add(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  // Check the number of arguments passed.
  if (args.Length() < 2) {
    // Throw an Error that is passed back to JavaScript
    isolate->ThrowException(Exception::TypeError(
        String::NewFromUtf8(isolate,
                            "Wrong number of arguments").ToLocalChecked()));
    return;
  }

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

  // Perform the operation
  double value =
      args[0].As<Number>()->Value() + args[1].As<Number>()->Value();
  Local<Number> num = Number::New(isolate, value);

  // Set the return value (using the passed in
  // FunctionCallbackInfo<Value>&)
  args.GetReturnValue().Set(num);
}

void Init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "add", Add);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

}  // namespace demo

You can use this addon from JavaScript like this:

// test.js
const addon = require("./build/Release/addon");

console.log("This should be eight:", addon.add(3, 5));

Real-World Applications

Addons can be used to extend Node.js in a variety of ways, including:

  • Performance: C/C++ code can be significantly faster than JavaScript code, so addons can be used to improve the performance of computationally intensive tasks.

  • Native APIs: Addons can access native APIs that are not available to JavaScript code, such as operating system calls and device drivers.

  • Hardware acceleration: Addons can be used to accelerate hardware-intensive operations, such as graphics rendering and video encoding.


Callbacks in Node.js Addons

What are callbacks?

Callbacks are functions that are passed to other functions and then executed by those functions at some later point in time. This allows us to break down large tasks into smaller units and execute them asynchronously.

How to use callbacks in Node.js addons?

In Node.js addons, you can pass JavaScript functions to C++ functions and execute them from there. Here's an example:

// addon.cc
#include <node.h>

// A function that takes a callback and executes it
void RunCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(args[0]);
  // Create an array of arguments to pass to the callback
  const unsigned argc = 1;
  v8::Local<v8::Value> argv[argc] = {
      v8::String::NewFromUtf8(isolate, "hello world").ToLocalChecked()
  };
  // Call the callback with the arguments
  callback->Call(isolate->GetCurrentContext(), v8::Null(isolate), argc, argv).ToLocalChecked();
}

// Initialize the addon and register the RunCallback function
void Init(v8::Local<v8::Object> exports, v8::Local<v8::Object> module) {
  NODE_SET_METHOD(module, "exports", RunCallback);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

How to use the addon?

// test.js
const addon = require("./build/Release/addon");

addon((msg) => {
  console.log(msg); // Prints: 'hello world'
});

Real-world applications:

Callbacks are used in a variety of real-world applications, such as:

  • Asynchronous I/O operations (e.g., reading from a file)

  • Event handling (e.g., responding to user input)

  • Timeouts and intervals (e.g., setting a timer to execute a function after a certain delay)


Object Factory in Node.js Addons

What is an Object Factory?

In Node.js, it's a feature that allows you to create and return new JavaScript objects from within a C++ function.

How It Works:

  1. Initialize the Object: Create a new JavaScript object using the Object::New() function.

  2. Set Properties: Add properties to the object using the Set() function.

  3. Return the Object: Pass the newly created object as the return value of your C++ function.

Code Example:

// addon.cc
#include <node.h>

using v8::Context;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void CreateObject(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = isolate->GetCurrentContext();

  Local<Object> obj = Object::New(isolate);
  obj->Set(context,
           String::NewFromUtf8(isolate,
                               "msg").ToLocalChecked(),
                               args[0]->ToString(context).ToLocalChecked())
           .FromJust();

  args.GetReturnValue().Set(obj);
}

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", CreateObject);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

JavaScript Example:

// test.js
const addon = require("./build/Release/addon");

const obj1 = addon("hello");
const obj2 = addon("world");
console.log(obj1.msg, obj2.msg);
// Prints: 'hello world'

Real-World Applications:

  • Creating custom data formats

  • Generating complex objects with interdependent properties

  • Building modular applications where objects can be easily created and exchanged

Advantages:

  • Enables the creation of complex objects in C++ that can be used in JavaScript.

  • Improves performance by avoiding the overhead of converting objects between JavaScript and C++.

  • Enhances code organization and modularity.


Function factory

Concept:

A function factory is a way to create JavaScript functions that wrap C++ functions and return them to JavaScript.

Simplified Explanation:

Imagine you have a factory that produces cars. In the same way, a function factory produces JavaScript functions. The C++ functions are like the car parts, and the JavaScript functions are like the finished cars.

Code Snippet:

// my_module.cc
#include <node.h>

using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

// The C++ function that will be wrapped
void MyFunction(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(
      isolate, "hello world").ToLocalChecked());
}

// The function factory
void CreateFunction(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  Local<Context> context = isolate->GetCurrentContext();
  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
  Local<Function> fn = tpl->GetFunction(context).ToLocalChecked();

  // Return the JavaScript function
  args.GetReturnValue().Set(fn);
}

// Initialize the module
void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", CreateFunction);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

JavaScript Example:

// test.js
const myModule = require("./build/Release/my_module");

const fn = myModule();
console.log(fn());
// Prints: "hello world"

Real-World Applications:

  • Creating custom JavaScript functions for use in Node.js applications.

  • Wrapping C++ libraries and exposing them to JavaScript.

  • Building reusable JavaScript components that can be easily integrated into different applications.


Wrapping C++ Objects

In Node.js, you can create C++ objects and expose them to JavaScript code. This allows you to extend Node.js with custom functionality written in C++.

Basic Example

Consider a C++ class called MyObject that you want to expose to JavaScript:

class MyObject {
public:
  double value;
};

To wrap this class in a way that allows JavaScript code to create new instances using the new operator, you can inherit from node::ObjectWrap:

class MyObject : public node::ObjectWrap {
public:
  MyObject(double value = 0) : value(value) {}

  // Constructor function for JavaScript
  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);

  // Function to increment the value
  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
};

Constructor Function

The New function is the constructor for the JavaScript class. It creates a new MyObject instance and wraps it in a JavaScript object:

void MyObject::New(const v8::FunctionCallbackInfo<v8::Value>& args) {
  double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
  MyObject* obj = new MyObject(value);
  obj->Wrap(args.This());
  args.GetReturnValue().Set(args.This());
}

Exposing a Method

The PlusOne function increments the value stored in the MyObject instance and returns the new value:

void MyObject::PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args) {
  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
  obj->value += 1;
  args.GetReturnValue().Set(Number::New(args.GetIsolate(), obj->value));
}

JavaScript Usage

In JavaScript, you can create instances of MyObject and call its methods like this:

const obj = new MyObject(10);
console.log(obj.plusOne()); // 11
console.log(obj.plusOne()); // 12
console.log(obj.plusOne()); // 13

Real-World Applications

Wrapping C++ objects can be useful for building custom modules that extend Node.js with:

  • High-performance numerical calculations

  • Database connectivity

  • Image processing

  • Machine learning models


Factory of wrapped objects

Explanation:

Instead of using the new keyword to create JavaScript objects, you can use a factory pattern. This pattern involves creating a function that returns a new object instance without using new.

How it works:

  1. In C++, you define a createObject() function in your addon (e.g., addon.cc). This function does not use new to create objects.

  2. In JavaScript, you call the createObject() function to get a new object instance. This is similar to calling a constructor, but without the new keyword.

Benefits:

  • Avoids explicit use of new.

  • Simplifies object creation and makes it more consistent with the JavaScript way of creating objects.

Code snippet:

// JavaScript
const createObject = require('./build/Release/addon');
const obj = createObject(10);
// C++
namespace demo {

void CreateObject(const FunctionCallbackInfo<Value>& args) {
  MyObject::NewInstance(args);
}

}  // namespace demo

Real-world application:

This pattern is useful in situations where you want to create objects in a consistent manner, without exposing the complexity of using the new keyword. For example, you could use this pattern to create objects that represent database models or service objects.

Improved versions or examples

Here's an improved version of the example above that uses a class method for object creation:

// JavaScript
class MyObject {
  constructor(value) {
    this.value = value;
  }

  plusOne() {
    this.value += 1;
    return this.value;
  }
}

const obj = new MyObject(10);
// C++
namespace demo {

class MyObject {
public:
  MyObject(double value = 0) : value_(value) {}

  double value() const { return value_; }

  void plusOne() { value_ += 1; }

private:
  double value_;
};

void CreateObject(const FunctionCallbackInfo<Value>& args) {
  const double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
  MyObject* obj = new MyObject(value);
  args.GetReturnValue().Set(obj);
}

}  // namespace demo

This version is more idiomatic JavaScript and allows you to use the new keyword to create objects.

Potential applications

  • Creating database models

  • Implementing service objects

  • Encapsulating complex object creation logic

  • Providing a consistent interface for object creation


Passing Wrapped Objects Around

When working with Node.js addons, it's possible to pass wrapped C++ objects around within your Node.js code. This is useful when you need to work with the same object in multiple functions or modules.

To pass a wrapped object around, you first need to unwrap it using the node::ObjectWrap::Unwrap() helper function. This function takes a JavaScript object as input and returns a pointer to the corresponding C++ object.

Once you have unwrapped the object, you can pass it to other functions or modules as needed. When you're done with the object, be sure to wrap it again using the ObjectWrap::Wrap() function. This will reattach the JavaScript object to the C++ object and allow you to continue working with it in Node.js code.

Here's an example of how to pass a wrapped object around in Node.js:

// myobject.h
#include <node.h>
#include <node_object_wrap.h>

namespace demo {

class MyObject : public node::ObjectWrap {
public:
  static void Init(v8::Isolate* isolate);
  static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
  inline double value() const { return value_; }

private:
  explicit MyObject(double value = 0);
  ~MyObject();

  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  static v8::Global<v8::Function> constructor;
  double value_;
};

}  // namespace demo
// myobject.cc
#include <node.h>
#include "myobject.h"

namespace demo {

using node::AddEnvironmentCleanupHook;
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Global;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

// Warning! This is not thread-safe, this addon cannot be used for worker
// threads.
Global<Function> MyObject::constructor;

MyObject::MyObject(double value) : value_(value) {
}

MyObject::~MyObject() {
}

void MyObject::Init(Isolate* isolate) {
  // Prepare constructor template
  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked());
  tpl->InstanceTemplate()->SetInternalFieldCount(1);

  Local<Context> context = isolate->GetCurrentContext();
  constructor.Reset(isolate, tpl->GetFunction(context).ToLocalChecked());

  AddEnvironmentCleanupHook(isolate, [](void*) {
    constructor.Reset();
  }, nullptr);
}

void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = isolate->GetCurrentContext();

  if (args.IsConstructCall()) {
    // Invoked as constructor: `new MyObject(...)`
    double value = args[0]->IsUndefined() ?
        0 : args[0]->NumberValue(context).FromMaybe(0);
    MyObject* obj = new MyObject(value);
    obj->Wrap(args.This());
    args.GetReturnValue().Set(args.This());
  } else {
    // Invoked as plain function `MyObject(...)`, turn into construct call.
    const int argc = 1;
    Local<Value> argv[argc] = { args[0] };
    Local<Function> cons = Local<Function>::New(isolate, constructor);
    Local<Object> instance =
        cons->NewInstance(context, argc, argv).ToLocalChecked();
    args.GetReturnValue().Set(instance);
  }
}

void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  const unsigned argc = 1;
  Local<Value> argv[argc] = { args[0] };
  Local<Function> cons = Local<Function>::New(isolate, constructor);
  Local<Context> context = isolate->GetCurrentContext();
  Local<Object> instance =
      cons->NewInstance(context, argc, argv).ToLocalChecked();

  args.GetReturnValue().Set(instance);
}

}  // namespace demo
// test.js
const addon = require("./build/Release/addon");

const obj1 = addon.createObject(10);
const obj2 = addon.createObject(20);
const result = addon.add(obj1, obj2);

console.log(result);
// Prints: 30

Real-World Applications

Passing wrapped objects around can be useful in a variety of real-world applications, such as:

  • Caching: You can wrap objects in Node.js code and then pass them to C++ code for caching. This can improve the performance of your application by avoiding the need to repeatedly create and destroy objects.

  • Data transfer: You can wrap objects in Node.js code and then pass them to C++ code for processing or storage. This can be useful for tasks such as data analysis or image processing.

  • Interfacing with native code: You can wrap objects in Node.js code and then pass them to C++ code for interfacing with native libraries. This can be useful for tasks such as accessing hardware devices or running external programs.