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.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,976