Mastering Node.js C++ Addons: Seamless Data Transfer Between JavaScript and C++
This article explains how to use Node.js C++ Addons and the V8 API to define, convert, and transfer JavaScript primitive and complex types to C++ and back, covering type inheritance, immutability, argument handling, and the role of NAN for version stability.
I love using Node.js, but for compute‑intensive scenarios it falls short, so C++ becomes a natural companion and Node.js offers C/C++ Addons via the V8 API.
JavaScript primitive types (String, Number, Boolean, null, undefined) inherit from
Primitive, which in turn inherits from
Value; V8 also provides integer types such as
Int32and
Uint32, and other structures like Object, Array, and Map.
All JavaScript values are stored in
Localobjects that represent runtime memory units.
The following snippet declares a
Numbervalue using the isolate of the V8 virtual machine:
#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)V8’s type API shows that basic JavaScript types are only declared, not assigned, because:
JavaScript primitives are immutable; variables point to memory cells, and reassignment creates a new cell.
Function arguments are passed by value, so changes in C++ do not affect the original JavaScript values.
Declaring a basic type with
Local<Value>creates a reference to a memory cell, and immutability prevents reassigning that reference.
Data Flow C++ → JavaScript
The demo below defines common JavaScript types (primitives, Object, Array, Function) in C++ and returns an object to JavaScript:
#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();
Local<Number> retval = v8::Number::New(isolate, 1000);
Local<String> str = v8::String::NewFromUtf8(isolate, "Hello World!");
Local<Object> obj = v8::Object::New(isolate);
obj->Set(v8::String::NewFromUtf8(isolate, "arg1"), str);
obj->Set(v8::String::NewFromUtf8(isolate, "arg2"), retval);
Local<FunctionTemplate> tpl = v8::FunctionTemplate::New(isolate, MyFunction);
Local<Function> fn = tpl->GetFunction();
fn->SetName(String::NewFromUtf8(isolate, "theFunction"));
obj->Set(v8::String::NewFromUtf8(isolate, "arg3"), fn);
Local<Boolean> flag = Boolean::New(isolate, true);
obj->Set(String::NewFromUtf8(isolate, "arg4"), flag);
Local<Array> arr = Array::New(isolate);
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);
Local<Value> und = Undefined(isolate);
obj->Set(String::NewFromUtf8(isolate, "arg6"), und);
Local<Value> nul = Null(isolate);
obj->Set(String::NewFromUtf8(isolate, "arg7"), nul);
args.GetReturnValue().Set(obj);
}
void init(Local<Object> exports, Local<Object> module) {
NODE_SET_METHOD(exports, "getTestValue", Test);
}
NODE_MODULE(returnValue, init)After compiling with
node-gyp, the addon can be used as:
const returnValue = require('./build/Release/returnValue.node');
console.log(returnValue.getTestValue());Data Flow JavaScript → C++
The following example shows how to validate argument count and types, convert JavaScript values to V8 types, and invoke a JavaScript function from C++:
#include <node.h>
#include <v8.h>
#include <iostream>
using namespace v8;
using namespace std;
void GetArgument(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 2) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments")));
return;
}
if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "arguments must be number")));
}
// ... type checks for Object, Boolean, Array, String, Function, Null, Undefined omitted for brevity
Local<Number> value1 = Local<Number>::Cast(args[0]);
Local<Number> value2 = Local<Number>::Cast(args[1]);
double sum = value1->NumberValue() + value2->NumberValue();
Local<String> str = Local<String>::Cast(args[2]);
String::Utf8Value utfStr(str);
cout << string(*utfStr) << endl;
Local<Array> input_array = Local<Array>::Cast(args[3]);
printf("%d, %f %f
", input_array->Length(), input_array->Get(0)->NumberValue(), input_array->Get(1)->NumberValue());
Local<Object> obj = Local<Object>::Cast(args[4]);
Local<Value> a = obj->Get(String::NewFromUtf8(isolate, "a"));
Local<Value> b = obj->Get(String::NewFromUtf8(isolate, "b"));
Local<Array> c = Local<Array>::Cast(obj->Get(String::NewFromUtf8(isolate, "c")));
cout << a->NumberValue() << " " << b->NumberValue() << endl;
printf("%d, %f %f
", c->Length(), c->Get(0)->NumberValue(), c->Get(1)->NumberValue());
Local<String> cString = Local<String>::Cast(c->Get(2));
String::Utf8Value utfC(cString);
cout << string(*utfC) << endl;
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 utfD1(dString1);
cout << string(*utfD1) << endl;
Local<String> dString2 = Local<String>::Cast(d->Get(String::NewFromUtf8(isolate, "n")));
String::Utf8Value utfD2(dString2);
cout << string(*utfD2) << endl;
Local<Boolean> flagTrue = Local<Boolean>::Cast(args[5]);
cout << "Flag: " << flagTrue->BooleanValue() << endl;
Local<Function> cb = Local<Function>::Cast(args[8]);
const unsigned argc = 2;
Local<Value> argv[2] = { a, b };
cb->Call(Null(isolate), argc, argv);
args.GetReturnValue().Set(sum);
}
void Init(Local<Object> exports, Local<Object> module) {
NODE_SET_METHOD(module, "exports", GetArgument);
}
NODE_MODULE(argumentss, Init)After compiling, it can be invoked as:
const getArguments = require('./build/Release/arguments');
console.log(getArguments(2, 3, 'Hello Arguments', [1,2,3], {a:10,b:100,c:[23,22,"我是33"],d:{m:'我是22',n:'我是23'}}, true, null, undefined, function myFunction(...args){
console.log('I am Function!');
console.log(...args);
console.log('I am Function!');
}));NAN
Because V8’s API changes across Node.js versions, the NAN library provides a stable wrapper; after including its header you can use helpers such as:
v8::Local<v8::Primitive> Nan::Undefined();
v8::Local<v8::Primitive> Nan::Null();Reference Materials
Type conversions from JavaScript to C++ in V8
node addon
v8 types documentation
node-gyp
gyp user documentation
nan
Taobao Frontend Technology
The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.