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
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.iOn Ubuntu
swig3.0 -javascript -node -c++ example.iJavascript 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
- 4,224