Back to articles

Node.js C/C++ module is actually simple

Node.js is one of the most unexpected technology for me JavaScript on the server side was an unbelievable thing for me 5 years ago. And to be honest, JavaScript was not a very straightforward technology with the main goal to handle HTML/CSS based UI. But now we have several successful Node.js projects in Kvinivel and it seems like everything will be done with JavaScript soon. And JavaScript itself becomes a much more serious and straightforward language. In my last post, I described possible ways to run asynchronous operations in Electron.

Another problem related to that electron project was problem of creating of C/C++ module for the Image Magic Library. There were several modules in npm some of them were just CLI wrappers some of them were wrappers on C++ API. Both of them seem to be wrappers created by somebody to solve their exact problem and do not solve my problems in addition CLI wrappers are slow. Thus, I decided to create one more limited wrapper just for my needs – imagemagik2 hope one day I will be able to make it more or less full-featured. But let me describe my experience with C/C++ Node.JS module creation…

You can find the source code here: https://github.com/TesserisPro/imagmagick2

What C/C++ Node.JS modules are?

Node.JS native module is a DLL (or equivalent for other OS) file that contains some code that interacts with Node.js through V8 API. This file is renamed to *.node by a specific build procedure. You will be able to manipulate JavaScript abstraction representation – create variables, functions, objects, control their parameters, etc. inside your C/C++ module.

Bad news:
— V8 API is extremely over-complicated and you should be an experienced C/C++ developer to use it.
— Node.js introduce additional abstraction layer Native Abstractions for Node.js (NAN) to simplify module programming that has very poor documentation, but you have to use it because without that abstraction your module may be not compatible with old or new versions of Node.js
— You have to recompile your module for every exact version of Node.js and for every platform, of course. If you try to use a module compiled for another version of node even if it has only minor changes, and you are on the same platform, you will see an error during loading this module like “Module version mismatch. Expected 48, got 47”.

Interesting news:
— Module building tool (node-gyp) enforces you to build cross-platform module
— You will need python to build your extension. It is not a problem actually, but it’s funny 🙂

Good news:
— It is not as complex as you thought at first 🙂

Hello world module

You can find a simple module sample in Node.js documentation.

Module startup

Seems to be obsolete

On Node.js web page you can find that the entry point of your module is void Init(Local exports, Local module) function and module registration should be done with NODE_MODULE(addon_name, Init). To add module content, you can just register all parts of your module by adding them to the exports’ parameter. Another option is to overwrite whole exports by function or anything you want. Exactly the same idea as in usual JavaScript modules module.exports = {...}.

The type Local is actually an object reference managed by the v8 garbage collector. According to V8 API documentation, there are two types of handles: local and persistent handles. Local handles are lightweight and transient and typically used in local operations. They are managed by HandleScopes. Persistent handles can be used when storing objects across several independent operations and have to be explicitly deal located when they’re no longer used.

Seems to be actual

According to NAN documentation, the entry point of NAN macros NAN_MODULE_INIT(init){ } and NODE_MODULE(your_module_name, init) where init is just an identifier and can be changed.

To add something to your exports you can use NAN macro NAN_EXPORT(target, your_object) the most confusing part here is target you never define it. It is just a naming convention defined in nan.h and node.h

// nan.h
#define NAN_MODULE_INIT(name) void name(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target)

// node.h
#define NODE_MODULE(modname, regfunc) NODE_MODULE_X(modname, regfunc, NULL, 0)

The NAN is full of macros that define hidden variables and other C language objects. That makes it very hard to understand.

The full code of module startup with the registration of method looks like following

#include "std.h"

NAN_METHOD(my_method) {
// Write code here;
}

NAN_MODULE_INIT(init) {
NAN_EXPORT(target, my_method);
}

NODE_MODULE(my_module, init);

Creating a function

To add some functionality to our module, you can create a function and register it. And here we have hidden identity again. Here is Marco from nan.h

<br />#define NAN_METHOD(name) Nan::NAN_METHOD_RETURN_TYPE name(Nan::NAN_METHOD_ARGS_TYPE info)

You can use info an argument to read function arguments in the following way

double myNumberValue = info[0].As<v8::Number>()->Value(); // First argument
Nan::Utf8String myStringValue(info[1].As<v8::String>()); // Second argument
char *actualString = *myStringValue; //Way to access string

As you can see here we have some NAN wrappers again, and this time Nan::Utf8String is a useful thing it saves several lines of code related to V8 string implementation.

To send the result of your calculations back to JavaScript world, you can set the return value by the following code.

// Create new V8 object
v8::Local<v8::Object> result = Nan::New<v8::Object>();

// Set some object fields
Nan::Set(result, v8::String::NewFromUtf8(Nan::GetCurrentContext()->GetIsolate(), "my_field_name"), Nan::New((int)my_filed_value));

// Set object as a return value
info.GetReturnValue().Set(result);

Here you can find v8::String::NewFromUtf8, unfortunately, I did not find a way to create a string with NAN, so I have to do it with V8 API. Also, a good point here is Nan::GetCurrentContext()-&gt;GetIsolate() that the method returns an object of the type Isolate* that object is required for most V8 API calls and represents something like V8 managed heap — a space where all variables live and die with GC.

Using async workers

In most cases, you want to create asynchronous functions to not block node.js main tread. You can use general C/C++ threads management, but V8 and Node.js are not threaded safe and if you call info.GetReturnValue().Set(result) from the wrong thread you can damage data, and you will get an exception for sure.

NAN introduces Nan::AsyncWorker a class with several virtual methods that should be overridden to crate async operation and simplify dispatching results back from another thread. The most important are HandleOKCallback, HandleErrorCallback, and Execute methods. Execute method is running in a separate thread and perform the asynchronous operation. HandleOKCallback is called in case Execute is finished without problems. HandleErrorCallback will be called in case of an error in Execute method. Thus, to implement asynchronous operation with callback you can inherit Nan::AsyncWorker class and override virtual methods in the following way.

class Worker : public Nan::AsyncWorker {
public:
Worker(Nan::Callback *callback) : AsyncWorker(callback)
{

}

void Execute()
{
if (do_my_asyc_action() != MY_SUCCESS_VALUE)
{
this->SetErrorMessage("Error!!!");
}
}
protected:
void HandleOKCallback()
{
v8::Local<v8::Value> argv[] = {
v8::String::NewFromUtf8(Nan::GetCurrentContext()->GetIsolate(), "Some result string")
};

// Call callback function
this->callback->Call(
1, // Number of arguments
argv); // Array of arguments
}

void HandleErrorCallback()
{
v8::Local<v8::Value> argv[] = {
v8::String::NewFromUtf8(Nan::GetCurrentContext()->GetIsolate(), this->ErrorMessage())
};

// Call callback function with error
this->callback->Call(1, argv);
}
};

Building your module

The build system configuration is more or less simple. You should create a JSON file named binding.gyp and add your source files and other options to this json file. The build will always be just a compilation of your C/C++ files. The node-gyp will automatically prepare building configurations for every platform during module installation. On Windows it will create solution/project files and build your module with Visual Studio, on Linux it will prepare to make files and build everything with gcc. Below you can find one of the simplest binding.gyp file.

{
"targets": [{
"target_name": 'my_module',
"sources": [ "main.cpp" ],
}]
}

Additionally, you can configure specific options for specific platforms. You can find more about node-gyp here https://github.com/nodejs/node-gyp.

To build your module automatically during installation, add the following script section to package.json

"scripts": {
"install": "node-gyp rebuild"
}

To build your module during development, you can use the node-gyp command with the build/rebuild parameter.

Conclusion

The V8 and NAN API are complicated and do not have very detailed documentation. Thus, my idea is to keep C/C++ module as simple as possible and use only methods and async workers without the creation of complex JavaScript structures through API. This will allow avoiding memory leaks (in some cases it is very hard to understand how and when you should free memory from docs) and other problems. You can add a complicated JavaScript wrapper on your simplified async methods inside your module and create rich and easy-to-use API. I used this approach in my module here https://github.com/TesserisPro/imagmagick2

It also could be interesting to You

About reinvention of a wheel, Agile and Linux way

How often in software development do you think about rewriting some 3rd party module from scratch? The popular vision of the problem says that we should not reinvent a wheel and use existing paid or…

“Why do people refuse to use WebRTC? Is it really its quality question?”

As a CEO who has 15+ years of software development, I take part very often in the first call with our clients. Very often, I can hear something like “We should like to move away…