NodeJS: Native Bindings

What are "bindings" ?

Dynamically-linked shared objects, written in C/C++, that are loadable into Node.js using require()

Node calls these modules "Addons"

A "binding" can refer to an Addon or an adapter library that enables smooth functioning of an Addon

Why Do We Care?

Bindings to native libraries form the core of the runtime

v8 - the base javascript runtime

libuv - the event loop

Internal Node Libraries - node::ObjectWrap

statically linked external libraries - OpenSSL

Why Do We Care?

Native libraries offer a large amount of prewritten functionality

Properly implemented native code

can offer better performance

When Should You Create Your Own Bindings?

NEVER!

ALWAYS write your code in javascript

Native Bindings leave you at the mercy of C/C++ developers

Native code adds certain complexities ie. memory allocation

Often slower than javascript due to the overhead involved in marshalling data

When Should You Create Bindings?

To extend the runtime

To leverage an existing code base that is too complex to recreate

or requires interop to function properly such as a UI toolkit (QT)

Wanna be a NodeJS core dev?

libpostal

A multilingual, international street address parser 

 

with bindings for PythonRubyGoJavaPHP, and NodeJS 

libpostal

Employs machine learning and natural language parsing

 

You do not want to try and rewrite this

How do we use bindings?

installed through npm like any other module

Be sure to install all native dependencies using your standard package manager

Installation will require python 2.7

if you receive an error and python is installed check the npmrc file

 

$ npm install node-postal
{
    "targets": [
        {
            "target_name": "expand",
            "sources": [
                "src/expand.cc"
            ],
            "libraries": [
                "-lpostal", "-L/usr/local/lib"
            ],
            "include_dirs": [
                "<!(node -e \"require('nan')\")",
                "/usr/local/include"
            ]
        },
        {
            "target_name": "parser",
            "sources": [
                "src/parser.cc"
            ],
            "libraries": [
                "-lpostal", "-L/usr/local/lib"
            ],
            "include_dirs": [
                "<!(node -e \"require('nan')\")",
                "/usr/local/include"
            ]
        }
    ]
}
#include <libpostal/libpostal.h>
#include <nan.h>

#define PARSER_USAGE "Usage: parse_address(address[, options])"

NAN_METHOD(ParseAddress) {
    if (info.Length() < 1) {
        Nan::ThrowTypeError(PARSER_USAGE);
        return;
    }

    if (!info[0]->IsString()) {
        Nan::ThrowTypeError(PARSER_USAGE);
    }

    Nan::Utf8String address_utf8(info[0]);
    char *address = *address_utf8;

    if (address == NULL) {
        Nan::ThrowTypeError("Could not convert first arg to string");
        return;
    }

    char *language = NULL;
    char *country = NULL;

    uint64_t i;

    address_parser_options_t options = get_libpostal_address_parser_default_options();

    if (info.Length() > 1 && info[1]->IsObject()) {
        v8::Local<v8::Object> props = info[1]->ToObject();
        v8::Local<v8::Array> prop_names = Nan::GetPropertyNames(props).ToLocalChecked();

        for (i = 0; i < prop_names->Length(); i++) {
            v8::Local<v8::Value> key = prop_names->Get(i);

            if (key->IsString()) {
                Nan::Utf8String utf8_key(key);
                char *key_string = *utf8_key;

                v8::Local<v8::Value> value = Nan::Get(props, key).ToLocalChecked();
                if (strcmp(key_string, "language") == 0) {
                    Nan::Utf8String language_utf8(value);
                    language = *language_utf8;
                    if (language != NULL) {
                        options.language = language;
                    }
                } else if (strcmp(key_string, "country") == 0) {
                    Nan::Utf8String country_utf8(value);
                    country = *country_utf8;
                    if (country != NULL) {
                        options.country = country;
                    }
                }
            }

        }

    }

    address_parser_response_t *response = parse_address(address, options);

    if (response == NULL) {
        Nan::ThrowError("Error parsing address");
        return;
    }

    v8::Local<v8::Array> ret = Nan::New<v8::Array>(response->num_components);

    v8::Local<v8::String> name_key = Nan::New("value").ToLocalChecked();
    v8::Local<v8::String> label_key = Nan::New("component").ToLocalChecked();

    for (i = 0; i < response->num_components; i++) {
        char *component = response->components[i];
        char *label = response->labels[i];

        v8::Local<v8::Object> o = Nan::New<v8::Object>();
        o->Set(name_key, Nan::New(component).ToLocalChecked());
        o->Set(label_key, Nan::New(label).ToLocalChecked());

        ret->Set(i, o);
    }

    address_parser_response_destroy(response);

    info.GetReturnValue().Set(ret);
}

static void cleanup(void*) {
    libpostal_teardown();
    libpostal_teardown_parser();
}

void init(v8::Local<v8::Object> exports) {
    if (!libpostal_setup() || !libpostal_setup_parser()) {
        Nan::ThrowError("Could not load libpostal");
        return;
    }

    exports->Set(Nan::New("parse_address").ToLocalChecked(), Nan::New<v8::FunctionTemplate>(ParseAddress)->GetFunction());

    node::AtExit(cleanup);
}

NODE_MODULE(parser, init)

NaN - WTF?

Native Abstractions For Node

A helper library to prevent breakage

SWIG

Simplified Wrapper and Interface Generator

Creates the "Glue Code"

Used as part of a build process it can help avoid problems arising from changes in the native library

Running SWIG

swig -javascript -node -c++ example.i

On Ubuntu

swig3.0 -javascript -node -c++ example.i

Javascript support requires 3.0 or newer

/* inchi.i */

%module inchi
%{
#include "inchi_api.h"

%}

extern  int  GetStringLength( char *p );
extern  int  GetStructFromINCHI( inchi_InputINCHI *inpInChI, inchi_OutputStruct *outStruct );
extern  int  GetStructFromStdINCHI( inchi_InputINCHI *inpInChI, inchi_OutputStruct *outStruct );
extern  void  FreeStructFromINCHI( inchi_OutputStruct *out );
extern  void  FreeStructFromStdINCHI( inchi_OutputStruct *out );
extern  int  GetINCHIfromINCHI( inchi_InputINCHI *inpInChI, inchi_Output *out );
extern  int  Get_inchi_Input_FromAuxInfo( 
                                                        char *szInchiAuxInfo, 
                                                        int bDoNotAddH, 
                                                        int bDiffUnkUndfStereo,
                                                        InchiInpData *pInchiInp );
extern  int  Get_std_inchi_Input_FromAuxInfo( char *szInchiAuxInfo, 
                                                        int bDoNotAddH, 
                                                        InchiInpData *pInchiInp );
                                                        
extern  void  Free_inchi_Input( inchi_Input *pInp );
extern  void  Free_std_inchi_Input( inchi_Input *pInp );
extern  int  CheckINCHI(const char *szINCHI, const int strict);    

extern  int  GetINCHIKeyFromINCHI(const char* szINCHISource, 
                                                              const int xtra1,
                                                              const int xtra2,
                                                              char* szINCHIKey, 
                                                              char* szXtra1, 
                                                              char* szXtra2);

extern  int  GetStdINCHIKeyFromStdINCHI(const char* szINCHISource, 
                                                                    char* szINCHIKey);
                                                                    
extern  int  CheckINCHIKey(const char *szINCHIKey);    

Example Output

#ifdef __cplusplus
#if 0
{ /* c-mode */
#endif
}
#endif


// Note: 'extern "C"'' disables name mangling which makes it easier to load the symbol manually
// TODO: is it ok to do that?
extern "C"
#if (NODE_MODULE_VERSION < 0x000C)
void SWIGV8_INIT (v8::Handle<v8::Object> exports)
#else
void SWIGV8_INIT (v8::Handle<v8::Object> exports, v8::Handle<v8::Object> /*module*/)
#endif
{
  SWIG_InitializeModule(static_cast<void *>(&exports));

  v8::HandleScope scope;
  v8::Handle<v8::Object> exports_obj = exports;


  // a class template for creating proxies of undefined types
#if (SWIG_V8_VERSION < 0x031900)
  SWIGV8_SWIGTYPE_Proxy_class_templ = v8::Persistent<v8::FunctionTemplate>::New(SWIGV8_CreateClassTemplate("SwigProxy"));
#else
  SWIGV8_SWIGTYPE_Proxy_class_templ.Reset(v8::Isolate::GetCurrent(), SWIGV8_CreateClassTemplate("SwigProxy"));
#endif

  /* create objects for namespaces */
  

  /* create class templates */
  

  /* register wrapper functions */
  

  /* setup inheritances */
  

  /* class instances */
  

  /* add static class functions and variables */
  SWIGV8_AddStaticFunction(exports_obj, "GetStringLength", _wrap_GetStringLength);
SWIGV8_AddStaticFunction(exports_obj, "GetStructFromINCHI", _wrap_GetStructFromINCHI);
SWIGV8_AddStaticFunction(exports_obj, "GetStructFromStdINCHI", _wrap_GetStructFromStdINCHI);
SWIGV8_AddStaticFunction(exports_obj, "FreeStructFromINCHI", _wrap_FreeStructFromINCHI);
SWIGV8_AddStaticFunction(exports_obj, "FreeStructFromStdINCHI", _wrap_FreeStructFromStdINCHI);
SWIGV8_AddStaticFunction(exports_obj, "GetINCHIfromINCHI", _wrap_GetINCHIfromINCHI);
SWIGV8_AddStaticFunction(exports_obj, "Get_inchi_Input_FromAuxInfo", _wrap_Get_inchi_Input_FromAuxInfo);
SWIGV8_AddStaticFunction(exports_obj, "Get_std_inchi_Input_FromAuxInfo", _wrap_Get_std_inchi_Input_FromAuxInfo);
SWIGV8_AddStaticFunction(exports_obj, "Free_inchi_Input", _wrap_Free_inchi_Input);
SWIGV8_AddStaticFunction(exports_obj, "Free_std_inchi_Input", _wrap_Free_std_inchi_Input);
SWIGV8_AddStaticFunction(exports_obj, "CheckINCHI", _wrap_CheckINCHI);
SWIGV8_AddStaticFunction(exports_obj, "GetINCHIKeyFromINCHI", _wrap_GetINCHIKeyFromINCHI);
SWIGV8_AddStaticFunction(exports_obj, "GetStdINCHIKeyFromStdINCHI", _wrap_GetStdINCHIKeyFromStdINCHI);
SWIGV8_AddStaticFunction(exports_obj, "CheckINCHIKey", _wrap_CheckINCHIKey);


  /* register classes */
  

  /* create and register namespace objects */
  
}

#if defined(BUILDING_NODE_EXTENSION)
NODE_MODULE(inchi, inchi_initialize)
#endif

Create a Gyp File

{
  "targets": [
    {
      "target_name": "inchi",
      "sources": [ "example.cxx", "inchi_wrap.cxx" ]
    }
  ]
}

Conclusion

You're 'bound' to need them and some point

Allow the use of native code from Node

Form the backbone of Node

It's just fun to learn something new

Further Reading:

Matt Sprague - lodolabs[at]gmail[dot]com

NodeJS: Native Bindings

By Matt Sprague

NodeJS: Native Bindings

  • 3,790