NodeJS C++ datatransfer
Because of a lack of documentation, I decided to write a short tutorial for exchanging data between NodeJS JavaScript and C++ for implementing native extensions. The extensions enable the usage of low level C++ apis and (in some cases) rewards the developer or user with performance benefits, when using correctly. The article requires JavaScript and C++ knowledge. If you don't want to know how to setup a nodejs c++ addon, directly jump to the JavaScript to C++ or C++ to JavaScript sections.
Preparations
In order to transfer data between JavaScript and C++ there has to exist a working NodeJS addon. In this section there is an explanation of the addon setup. Create the following folder structure.
At first install required dependencies with
yarn add node-gyp nan --dev
And add the following content to your binding.gyp file
{
"targets": [
{
"target_name": "addon",
"sources": ["src/addon-class.cc", "src/addon.cc"],
"include_dirs": ["<!(node -e \"require('nan')\")"]
}
]
}
Afterwards initialize the node module with the following content in your addon.cc file.
#include <nan.h>
#include "addon-class.h"
NAN_MODULE_INIT(InitAll) {
Numscript::Init(target);
}
NODE_MODULE(numscript, InitAll)
The addon class header addon-class.h looks like follows
#ifndef ADDONCLASS_H
#define ADDONCLASS_H
#include <nan.h>
class AddonClass : public Nan::ObjectWrap {
public:
static NAN_MODULE_INIT(Init);
private:
AddonClass();
~AddonClass();
static NAN_METHOD(New);
static NAN_METHOD(setup);
static NAN_METHOD(simulate);
Nan::Persistent<v8::Function> constructor;
};
#endif
And in the classes source file addon-class.cc you can implement the functions in the following manner.
NAN_MODULE_INIT(Numscript::Init) {
v8::Local<v8::FunctionTemplate> ctor = Nan::New<v8::FunctionTemplate>(New);
auto ctorInst = ctor->InstanceTemplate();
ctorInst->SetInternalFieldCount(1);
// This is where the fun begins.
target->Set(Nan::New("AddonClass").ToLocalChecked(), ctor->GetFunction());
}
And this is how we create the class instance.
NAN_METHOD(Numscript::New) {
if (info.IsConstructCall()) {
Numscript *obj = new Numscript();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
return;
}
In the package.json file add the building script, to configure node-gyp and build node-gyp.
"scripts": {
"build": "node-gyp configure && node-gyp build"
}
And finally you should be able to run yarn build
to run the build script.
JavaScript to C++
Working with function arguments is essential for working with C++ addons, e.g. for passing configurations or arrays of data to work with. When declaring a function as NAN_METHOD(FunctionName)
(which is a helper macro for static void FunctionName(const Nan::FunctionCallbackInfo& info)
) there is the info parameter which contains the JS object instance itself and all Javascript function parameters as an array.
For an examplary javascript function myFunction(callback: () => void, runs: number, title: string) => void
the regarding C++ could be
Nan::Callback *callback = new Nan::Callback(Nan::To<v8::Function>(info[0]).ToLocalChecked());
int runs = info[1]->Uint32Value();
std::string name = *v8::String::Utf8Value(info[2]->ToString());
In addition to parsing there are several helper functions to check the argument type. They all return c++ boolean values.
info[0]->IsFunction()
info[0]->IsString()
info[0]->IsNumber()
This is very handy with the nan error handling
return Nan::ThrowTypeError(
"Argument must be a string");
}
C++ to JavaScript
If one calls a native c++ function from within JavaScript, it (in many cases) expects a return value. One can set a return value of type v8::Local<T>
with the following example
NAN_METHOD(Numscript::setup) {
info.GetReturnValue().Set(Nan::New<v8::String>("Hello world").ToLocalChecked());
}
New objects or primitives can be created with the
Nan::New()
method. It has several overwrites. If the parameter is kind of string (e.g. char[]
, std::string
) it returns a MaybeLocal
object of generic type T which can be checked by .ToLocalChecked()
and returns a v8::Local<T>
. On the other hand there is no need to local check numbers and other primitives.
Primitives
To create primitives of type string, number, boolean, undefined or null as v8::Local<T>
, use the following code snippets.
Nan::New("string").ToLocalChecked()
Nan::New<v8::Number>(42)
Nan::True()
Nan::False()
Nan::Undefined()
Nan::Null()
Objects
It is a little more complex to create objects. Here you have got several options.
v8::Local<v8::ObjectTemplate> objTemplate = Nan::New<v8::ObjectTemplate>();
objTemplate->Set(Nan::New("prop").ToLocalChecked(), Nan::New<v8::Number>(42));
Imagine you want to access the setup function as a property of an object, you can do the following
v8::Local<v8::FunctionTemplate> fn = Nan::New<v8::FunctionTemplate>(setup);
objTemplate->Set(Nan::New("setup").ToLocalChecked(), fn);
And finally bind the object template to the addon instance by
Nan::SetPrototypeTemplate(ctor, "myObj", objTemplate);