Type conversion between Node.js and C++ - Alibaba Cloud Developer Forums: Cloud Discussion Forums

Adolph
Engineer
Engineer
  • UID623
  • Fans4
  • Follows1
  • Posts72
Reads:4868Replies:0

[Share]Type conversion between Node.js and C++

Created#
More Posted time:Oct 27, 2016 13:45 PM
While I like using Node.js very much, it fails to perform well for compute-intensive scenarios. Under such circumstances, C++ is a good choice. It is very lucky for us that Node.js provides an official C/C++ Addons mechanism for us to combine Node.js and C++ using V8 APIs.
Despite the many documents on the official Node.js website regarding how to use the APIs, it is still very effort-consuming to transmit data between JavaScript and C++. C++ is a strongly-typed language (”1024” is a character string type instead of an integer type), while JavaScript always performs type conversion for us by default.
The basic types of JavaScript include string, number, boolean, null, and undefined. V8 defines these types through the class inheritance method. All these types inherit from the Primitive class, while the Primitive class inherits from the Value. V8 also supports the integer type (including Int32 and Uint32), and all the type definitions can be found in V8 Type Documents. Apart from basic types, there are also definitions of the object, array and map types.
The inheritance relationships of basic types can be found in the figure below:

In V8, all the JavaScript values are placed in the Local object, and the memory unit during JavaScript runtime is specified through this object.
The code below defines a value of the Number type. To be specific, the isolate variable declared in the Test function represents the heap memory in the V8 VM. It is used when a new variable is created. The following row of code declares a variable of the Number type through isolate.
#include <node.h>
#include <v8.h>

using namespace v8;

void Test(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = args.GetIsolate();
    // Declare Variable
    Local<Number> retval = v8::Number::New(isolate, 1000);
}

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


NODE_MODULE(returnValue, init)
Having viewed the V8 Type API Documents, you will find there are only variable declarations for basic JavaScript types, with no value assignment of the variables. This may seem very strange at first, but after a second thought, it turns out to be reasonable. The main reasons include the following:
 The basic types of JavaScript are immutable and all the variables point to an immutable memory unit. For example, if var a = 10, the value contained in the memory unit that a points to is 5. Reassign the value and make a = 100. The value of the memory unit is not changed, but a points to another memory unit in which the value is 100. If two variables x and y are both declared to be 10 in value, they point to the same memory unit.
 To pass a parameter by a function, the parameter value, instead of the reference, is actually passed. When C++ function is called in JavaScript, if the parameter is of a basic type, the parameter value is copied to the destination at every call, and the change to the parameter value will not affect the original value.
 Variables using Local<Value> to declare the basic types are all references of the memory unit. Because of the first reason, the reference value won’t be changed to make it point to another memory unit, and there is no value re-assignment of variables here.
Data flow C++ > JavaScript
The demo below defines some frequently-used JavaScript types, including basic types and the Object, Array and Function.
#include <node.h>
#include <v8.h>

using namespace v8;

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

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

    // Number Declare Type
    Local<Number> retval = v8::Number::New(isolate, 1000);

    // String Declare Type
    Local<String> str = v8::String::NewFromUtf8(isolate, "Hello World!");

    // Object Declare Type
    Local<Object> obj = v8::Object::New(isolate);
    // object assignment
    obj->Set(v8::String::NewFromUtf8(isolate, "arg1"), str);
    obj->Set(v8::String::NewFromUtf8(isolate, "arg2"), retval);

    // Function object type and assignment
    Local<FunctionTemplate> tpl = v8::FunctionTemplate::New(isolate, MyFunction);
    Local<Function> fn = tpl->GetFunction();
    // Function Name
    fn->SetName(String::NewFromUtf8(isolate, "theFunction"));
    obj->Set(v8::String::NewFromUtf8(isolate, "arg3"), fn);

    // Boolean Declare Type
    Local<Boolean> flag = Boolean::New(isolate, true);
    obj->Set(String::NewFromUtf8(isolate, "arg4"), flag);

    // Array Declare Type
    Local<Array> arr = Array::New(isolate);
    // Array Assignment
    arr->Set(0, Number::New(isolate, 1));
    arr->Set(1, Number::New(isolate, 10));
    arr->Set(2, Number::New(isolate, 100));
    arr->Set(3, Number::New(isolate, 1000));
    obj->Set(String::NewFromUtf8(isolate, "arg5"), arr);

    // Undefined Declare Type
    Local<Value> und = Undefined(isolate);
    obj->Set(String::NewFromUtf8(isolate, "arg6"), und);

    // null Declare Type
    Local<Value> null = Null(isolate);
    obj->Set(String::NewFromUtf8(isolate, "arg7"), null);

    // Return value for JavaScript callback
    args.GetReturnValue().Set(obj);
}

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

NODE_MODULE(returnValue, init)


All the addons require an Initialize function, as shown in the code below:
void Initialize(Local<Object> exports);
NODE_MODULE(module_name, Initialize)


Initialize is the initialize function, module_name is the binary file name generated after compilation, and the module name of the above code is returnValue.
The above code, after being compiled through node-gyp (the compilation process is detailed in the official C/C++ Addons document), can be called by the following methods.
// The returnValue.node file is the file generated after compilation. Its file name is determined through NODE_MODULE(returnValue, init)
const returnValue = require('./build/Release/returnValue.node');
console.log(returnValue.getTestValue());


The execution results are as follows:


Data flow JavaScript > C++
The demo above demonstrates how to define the JavaScript type in C++. The data flows from C++ to JavaScript. The data also needs to flow from JavaScript to C++, that is, some parameters need to be passed for calling C++ functions.
The code below demonstrates the judgment on the number of parameters, the parameter types, and the process of converting parameter types to V8, including basic types and the Object, Array, and Function.
#include <node.h>
#include <v8.h>
#include <iostream>

using namespace v8;
using namespace std;

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

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

    // arguments type judgement
    if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
        //throw exception
        isolate->ThrowException(Exception::TypeError(
            String::NewFromUtf8(isolate, "argumnets must be number")));
    }

    if (!args[0]->IsObject()) {
        printf("I am not Object\n");
    }

    if (!args[0]->IsBoolean()) {
        printf("I am not Boolean\n");
    }

    if (!args[0]->IsArray()) {
        printf("I am not Array\n");
    }

    if (!args[0]->IsString()) {
        printf("I am not String\n");
    }

    if (!args[0]->IsFunction()) {
        printf("I am not Function\n");
    }

    if (!args[0]->IsNull()) {
        printf("I am not Null\n");
    }

    if (!args[0]->IsUndefined()) {
        printf("I am not Undefined\n");
    }

    // js Number type convert to v8 Number type
    Local<Number> value1 = Local<Number>::Cast(args[0]);
    Local<Number> value2 = Local<Number>::Cast(args[1]);
    double value = value1->NumberValue() + value2->NumberValue();

    // js String type convert to v8 String type
    Local<String> str = Local<String>::Cast(args[2]);
    String::Utf8Value utfValue(str);
    cout<<string(*utfValue)<<endl;

    // js Array type convert to v8 Array type
    Local<Array> input_array = Local<Array>::Cast(args[3]);
    printf("%d, %f %f\n", input_array->Length(), input_array->Get(0)->NumberValue(), input_array->Get(1)->NumberValue());

    // js Object type convert to v8 Object type
    Local<Object> obj = Local<Object>::Cast(args[4]);

    // Get Object value from key
    Local<Value> a = obj->Get(String::NewFromUtf8(isolate, "a"));
    Local<Value> b = obj->Get(String::NewFromUtf8(isolate, "b"));

    // js Array type convert to v8 Array type
    Local<Array> c = Local<Array>::Cast(obj->Get(String::NewFromUtf8(isolate, "c")));
    cout<<a->NumberValue()<<"   "<<b->NumberValue()<<endl;
    printf("%d, %f %f\n", c->Length(), c->Get(0)->NumberValue(), c->Get(1)->NumberValue());

    // js String type convert to v8 String type
    Local<String> cString = Local<String>::Cast(c->Get(2));
    String::Utf8Value utfValueD(cString);
    cout<<string(*utfValueD)<<endl;

    // Get Object value from key
    Local<Object> d = Local<Object>::Cast(obj->Get(String::NewFromUtf8(isolate, "d")));
    Local<String> dString1 = Local<String>::Cast(d->Get(String::NewFromUtf8(isolate, "m")));
    String::Utf8Value utfValued1(dString1);
    cout<<string(*utfValued1)<<endl;

    // Get Object value from key
    Local<String> dString2 = Local<String>::Cast(d->Get(String::NewFromUtf8(isolate, "n")));
    String::Utf8Value utfValued2(dString2);
    cout<<string(*utfValued2)<<endl;

    // js Booelan type convert to v8 Boolean type
    Local<Boolean> FlagTrue = Local<Boolean>::Cast(args[5]);
    cout<<"Flag: "<<FlagTrue->BooleanValue()<<endl;

    // js Function type convert to v8 Function type
    Local<Function> cb = Local<Function>::Cast(args[8]);
    const unsigned argc = 2;
    Local<Value> argv[2];
    argv[0] = a;
    argv[1] = b;
    cb->Call(Null(isolate), argc, argv);

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

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

NODE_MODULE(argumentss, Init)


The code, after being compiled through node-gyp, can be called through the methods below.
const getArguments = require('./build/Release/arguments');

console.log(getArguments(2, 3, 'Hello Arguments', [1, 2, 3], {
        a: 10,
        b: 100,
        c: [23, 22, "I am 33"],
        d: { m: 'I am 22', n: 'I am 23' }
    }, true, null, undefined,
    function myFunction(...args) {
        console.log('I am Function!');
        console.log(...args);
        console.log('I am Function!');
    }));


The execution results are as follows:


I will not introduce the other types one by one here. The corresponding V8 APIs can be found in the documents.
NAN
Since the V8 APIs are not stabilized yet, the APIs related to different versions of Node.js types may change and NAN helps with the encapsulation. You don’t need to care about the version while coding, but only need to introduce the corresponding header files.
After the header file is introduced, you can use the following methods:
v8::Local<v8::Primitive> Nan::Undefined()
v8::Local<v8::Primitive> Nan::Null()


Reference
Type conversions from JavaScript to C++ in V8
node addon
v8 types documentation
node-gyp
nan
Guest