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.
"What is that strange bird? His eyes are very funny and he's a bit chubby. Chirp."

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')\")" ] } ] }
Listing: binding.gyp
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)
Listing: addon.cc
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
Listing: addon-class.h
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()); }
Listing: addon-class.cc
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; }
Listing: addon-class.cc
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" }
Listing: package.json
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);
References NodeJS C++ docs Native abstractions for NodeJS Stackoverflow - Binding nested objects to NodeJS addon

Comments

Any questions? Want to join the discussion?

Sign in or sign up