For JavaScript programmers, Node.js is really our preferred language for developing as a server. The performance advantage of Node.js is that it uses Google’s V8 engine, uses a non-blocking I/O model, and is event-driven. But Node.js doesn’t necessarily perform well in computationally intensive scenarios. Fortunately, there is a C++ Addons mechanism that allows us to write native C++ modules and call them from node.js.

Why use itC++The module

  • C++The community is huge and I want to be in our ready-madeNode.jsThe application uses aC++The module.
  • Computationally intensive scenarios with high performance requirements.

Here’s an example:Fabonacci

Fibonacci sequences are usually solved recursively, and here, to reflect the advantages of calling C++ modules in node.js, we don’t use caching in Fabonacci.

In Node.js, according to the Fabonacci definition, we write the following code, fabonacci.js:

// fabonacci.js
function fabonacciNodeJS(n) {
  if (n === 0) {
    return 0;
  }
  if (n === 1) {
    return 1;
  }
  return fabonacciNodeJS(n - 1) + fabonacciNodeJS(n - 2);
}

function TestFabonnacci(func, env, n) {
  const start = (new Date()).getTime();
  const result = func(n);
  const end = (new Date()).getTime();
  console.log(`fabonacci(${n}) run in ${env} result is ${result}, cost time is ${end - start} ms.`);
}

TestFabonnacci(fabonacciNodeJS, 'Native Node.js'.40);
Copy the code

You can run this program from the command line with the following result:

fabonacci(40) run in Native Node.js result is 102334155, cost time is 1125 ms.
Copy the code

In order to show the advantages of using C++ extension modules in node.js for intensive computing scenarios, I wrote the following code according to C++ Addons, fabonacci. Cc:

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

namespace fabonacci {

  using namespace v8;

  static inline size_t runFabonacci(size_t n) {
    if (n == 0)
    {
      return 0;
    }
    if (n == 1)
    {
      return 1;
    }
    return runFabonacci(n - 1) + runFabonacci(n - 2);
  }

  static void Fabonacci(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    // Check the parameter type
    if(! args[0]->IsNumber())
    {
      isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "argument type must be Number")));
    }
    size_t n = args[0]->NumberValue();
    Local<Number> num = Number::New(isolate, runFabonacci(n));
    args.GetReturnValue().Set(num);
  }

  void init(Local<Object> exports, Local<Object> module) {
    NODE_SET_METHOD(module."exports", Fabonacci);
  }

  NODE_MODULE(NODE_GYP_MODULE_NAME, init)

}
Copy the code

Modify the previous fabonacci. Js and test the above C++ extensions:

// fabonacci.js
const fabonacciCPP = require('./build/Release/fabonacci');

function fabonacciNodeJS(n) {
  if (n === 0) {
    return 0;
  }
  if (n === 1) {
    return 1;
  }
  return fabonacciNodeJS(n - 1) + fabonacciNodeJS(n - 2);
}

function TestFabonnacci(func, env, n) {
  const start = (new Date()).getTime();
  const result = func(n);
  const end = (new Date()).getTime();
  console.log(`fabonacci(${n}) run in ${env} result is ${result}, cost time is ${end - start} ms.`);
}

TestFabonnacci(fabonacciNodeJS, 'Native Node.js'.40);
TestFabonnacci(fabonacciCPP, 'C++ Addon'.40);

Copy the code

Run the above program, the result is as follows:

fabonacci(40) run in Native Node.js result is 102334155, cost time is 1120 ms.
fabonacci(40) run in C++ Addon result is 102334155, cost time is 587 ms.
Copy the code

As you can see, calling the C++ extension module in node.js to calculate the Fibonacci number of n = 40 is nearly twice as fast.

fromHello Worldstart

Now, we can start by writing a Hello World to show how to write a C++ extension and call it in the node.js module:

Here is a Hello World module written in C++ Addons that can be called in node.js code.

#include <node.h>

namespace helloWorld {

  using namespace v8;

  static void HelloWorld(const FunctionCallbackInfo<Value>& args) {
    // ISOLATE The current V8 execution environment. Each ISOLATE execution environment is independent of each other
    Isolate* isolate = args.GetIsolate();
    // Set the return value
    args.GetReturnValue().Set(String::NewFromUtf8(isolate, "Hello, World!"));
  }

  static void init(Local<Object> exports, Local<Object> module) {
    // set module.exports to HelloWorld
    NODE_SET_METHOD(module."exports", HelloWorld);
  }
  // All Node.js plug-ins must have the following pattern of initialization functions
  NODE_MODULE(NODE_GYP_MODULE_NAME, init)

}
Copy the code

The above C++ code is equivalent to the following JavaScript code:

module.exports.hello = (a)= > 'world';
Copy the code

First, create a file called binding.gyp in the project root directory as follows:

{
  "targets": [{"target_name": "fabonacci"."sources": [ "fabonacci.cc"]]}}Copy the code

Binding. gyp uses a JSON-like format to describe the build configuration of the module. Sudo NPM install -g node-gyp install -g node-gyp

Execute in project root:

node-gyp configure
node-gyp build
Copy the code

After a successful build, the executable fabonacci. Node will be in the /build/Release directory of the project root directory. We can import this module in Node.js:

const hello = require('./build/Release/hello');
console.log(hello()); // Hello, World!
Copy the code

Conversion of V8 data types to JavaScript data types

V8 data types are converted to JavaScript data types

Data declared using V8 ::Local< V8 ::Value> according to the V8 documentation will be managed by V8’s Garbage Collector. Let’s write the following C++ module example, declare the following type V8 variable in the C++ module, and export it to the JavaScript module for use:

#include <node.h>

namespace datas {
  using namespace v8;

  static void MyFunction(const FunctionCallbackInfo<Value> &args) {
    Isolate* isolate = args.GetIsolate();
    args.GetReturnValue().Set(String::NewFromUtf8(isolate, "MyFunctionReturn"));
  }

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

    // Declare a V8 Object variable
    Local<Object> object = Object::New(isolate);
    // Declare a V8 variable of type Number
    Local<Number> number = Number::New(isolate, 0);
    // Declare a V8 String variable
    Local<String> string = String::NewFromUtf8(isolate, "string");
    // Declare a V8 Function variable
    Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
    Local<Function> func = tpl->GetFunction();
    // Declare a V8 Array variable
    Local<Array> array = Array::New(isolate);
    // Assign to array
    for (int i = 0; i < 10; ++i)
    {
      array->Set(i, Number::New(isolate, i));
    }
    // Declare a V8 Boolean variable
    Local<Boolean> boolean = Boolean::New(isolate, true);
    // Declare a V8 Undefined variable
    Local<Value> undefined = Undefined(isolate);
    // Declare a V8 Null variable
    Local<Value> nu = Null(isolate);
    // Set the name of the function
    func->SetName(String::NewFromUtf8(isolate, "MyFunction"));
    // Assign a value to the object
    object->Set(String::NewFromUtf8(isolate, "number"), number);
    object->Set(String::NewFromUtf8(isolate, "string"), string);
    object->Set(String::NewFromUtf8(isolate, "function"), func);
    object->Set(String::NewFromUtf8(isolate, "array"), array);
    object->Set(String::NewFromUtf8(isolate, "boolean"), boolean);
    object->Set(String::NewFromUtf8(isolate, "undefined"), undefined);
    object->Set(String::NewFromUtf8(isolate, "null"), nu);
    args.GetReturnValue().Set(object);
  }

  static void init(Local<Object> exports, Local<Object> module) {
    NODE_SET_METHOD(module."exports", Datas);
  }

  NODE_MODULE(NODE_GYP_MODULE_NAME, init)
}
Copy the code

Use the Node-gyp tool to build the above modules and introduce them into the Node.js module:

const datas = require('./build/Release/datas');
console.log(datas());
Copy the code

Running results:

JavaScript data type converted to V8 data type

For example, if we pass a JavaScript variable of the Number datatype in the parameter, we can use the V8 ::Number::Cast method to convert the JavaScript datatype to v8 datatype. We create the following module factory.cc, an example of factory pattern creation objects:

#include <node.h>

namespace factory {
  using namespace v8;

  static void Factory(const FunctionCallbackInfo<Value> &args) {
    Isolate* isolate = args.GetIsolate();
    Local<Object> object = Object::New(isolate);

    object->Set(String::NewFromUtf8(isolate, "name"), Local<String>::Cast(args[0])); The Cast method implements JavaScript conversion to V8 data types
    object->Set(String::NewFromUtf8(isolate, "age"), Local<Number>::Cast(args[1])); The Cast method implements JavaScript conversion to V8 data types
    args.GetReturnValue().Set(object);
  }

  static void init(Local<Object> exports, Local<Object> module) {
    NODE_SET_METHOD(module."exports", Factory);
  }

  NODE_MODULE(NODE_GYP_MODULE_NAME, init)
}
Copy the code

Call the above module:

const factory = require('./build/Release/factory');
console.log(factory('counter'.21)); // { name: 'counter', age: 21 }
Copy the code

Refer to the V8 documentation for other types of Cast calls.