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

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

Another problem related to that electron project was problem of creation of C/C++ module for Image Magic library. There was several modules in npm some of them was just CLI wrappers some of them was wrappers on C++ API. Both of them seems to be a 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 les full featured. But let me describe my experience with C/C++ Node.JS module creation…

You can find 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 specific build procedure. You will be able to manipulate JavaScript abstraction representation – crete variables, functions, object control their parameters, etc. inside your C/C++ module.

Bad news:
– V8 API is extremely over-complicated and you should be 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 will try to use module compiled for another version of node even if it will have only minor changes and you are on the same platform you will see 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 so complex as you thought first 🙂

Hello world module

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

Module startup

Seems to be obsolete

On Node.js web page you can find that 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 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 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 light-weight 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 deallocated when they’re no longer used.

Seems to be actual

According to NAN documentation entry point of are 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 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 defines hidden variable and other C language objects. That makes it very hard to understand.

Full code of module startup with 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 identified 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 argument to read function arguments in 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 useful thing it saves several lines of code related to V8 string implementation.

To send result of your calculations back to JavaScript world you can set return value by 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 return value
info.GetReturnValue().Set(result);

Here you can find v8::String::NewFromUtf8, unfortunately I did not found a way to create string with NAN, so I have to do it with V8 API. Also good point here is Nan::GetCurrentContext()-&gt;GetIsolate() that method returns object of type Isolate* that object is required for most V8 API calls and represent 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 thread safe and if you call info.GetReturnValue().Set(result) from wrong thread you can damage data and you will get exception for sure.

NAN introduce Nan::AsyncWorker a class with several virtual methods that should be overridden to crate async operation and simplify dispatching results back from another thread. Most important are HandleOKCallback, HandleErrorCallback and Execute methods. Execute method is running in separate thread and perform asynchronous operation. HandleOKCallback is called in case Execute finished without problems. HandleErrorCallback will be called in case of error in Execute method. Thus to implement asynchronous operation with callback you can inherit Nan::AsyncWorker class and override virtual methods in 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 be always just 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 makefile and build everything with gcc. Bellow 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 following script section to package.json

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

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

Conclusion

The V8 and NAN API are complicated and have not very detailed documentation thus my idea is to keep C/C++ module as simple as possible and use only methods and async workers without creation of complex JavaScript structures through API. This will allow to avoid 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 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

5 Comments

  1. sv
    Posted December 22, 2016 at 16:31 | Permalink | Reply

    Hey, great articles (this and the previous one)!

    I’ve just started looking into Electron, and wonder how difficult would it be to debug those C++ modules, preferably in an IDE, and not via gdb/lldb or whatever is available on Windows?

    Thanks.

    Like

    • Posted December 23, 2016 at 16:35 | Permalink | Reply

      Not sure that it is possible. This stupid idea to rename *.dll to *.node may brake everything. Quick answer is that you need *.pdb file, source files and just attach debugger to running node process. But not sure how to generate *.pdb with gyp. My understanding that you should avoid debugging of C/C++ node modules by creating simple and testable functions in your module that can be tested and debugged outside node.

      Like

      • sv
        Posted December 23, 2016 at 21:06 | Permalink

        Thanks for you comment, this info will surely come handy. Happy New Year!

        Like

  2. Posted May 3, 2017 at 13:25 | Permalink | Reply

    Скажите у Вас есть возможность пожсказать как использовать библиотеку C DLL в node.js? Я нашол информацию о том что можно использовать nofr-ffi.Но не совсем понятно как. Спасибо!

    Like

    • Posted May 3, 2017 at 14:25 | Permalink | Reply

      Добрый день Владислав, да конечно наша компания может это сделать более того мы можем написать удобную обертку на DLL которую вы хотите использовать, если интересует пишите на peleshenko@tesseris.com (только не с mail.ru 🙂 )

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: