Ataraxia Consulting

Peace of mind for consulting

Binary addon design

When writing your application in Node you may need to communicate with an external library or device that may be impractical or impossible to do from pure JavaScript. It may be that the overhead added from [ffi](http://npm.im/ffi) is also too much for your application. Or -- the least likely scenario -- you have demonstrable proof that your compiler can generate a faster version of your algorithm than the V8 JIT can (while this might be easy to confirm on the micro case, it's quite unlikely to be true for everyone given the varying CPUs and compiler combinations in use). If you find yourself in one of these rare cases, it is necessary to write a [binary addon](http://nodejs.org/api/addons.html). If you need to write a binary addon, here are some basic guidelines to keep in mind: (When "native layer" is used, it's meant to refer to actions performed in the C/C++ side, vs what happens in JavaScript) # Opt Out Just to reinforce what I said before, by writing this module in C/C++ you're explicitly opting out of any runtime benefit the JIT may provide you. Remember the JIT will watch execution of your functions and optimize and inline those functions on the fly. You will need to strike a good balance between what is done in JavaScript and what is done in C/C++. I would vote to do as little as possible on the C/C++ side, just enough to unpack and pack the values you are passing back and forth between the library and JavaScript. # JavaScript for consumers To help enforce the previous rule, it's best to wrap a method defined in C/C++ that's meant to be consumed by other users with a JavaScript shim. That is if you `NODE_SET_METHOD(target, "foo", Foo)` you shouldn't be doing `exports.foo = binding.foo`. This puts the burden of argument checking and validation in the native layer. Instead consider:
exports.foo = function(bar, baz) {
  if (isNaN(baz) || baz > 10)
    throw new Error("u did it wrong");
  binding.foo(''+bar, +baz);
};
Since we're passing `bar` coerced as a string and validating `baz`'s input, we can make more assumptions about the safety of certain actions in the native layer. We're here to write JavaScript, so let's actually do that whenever possible. # Cheap boundary It is pretty darn cheap to cross the JavaScript and C/C++ boundary, this is unlikely to be the bottleneck in your application. Write and design what feels comfortable and is easy for you to understand and maintain. With the following caveat: # Primitives Please You will get the best bang for your buck if you interact only with "primitives", like: * `v8::Number` or `v8::Integer` * `v8::String` * `v8::Function` * `node::Buffer` * `v8::External` * `v8::Array` The methods you export from your binding should avoid (like the plague) creating, inspecting, or mutating complex objects from the native layer. It's extra ordinarily slow. It's fast in every day JavaScript usage because the JIT gets to do all sorts of fancy caching and inlining, which you won't get because you explicitly opted out. Just in case there's any confusion, if the method defined in the native layer will be called often make sure it does **not** call `->Get` and especially not `->Set` on an object passed to it. Doing so will only make you sad. Actually, it's ok to call these methods on relatively small `v8::Array`'s because you are just doing normal pointer arithmetic to get the values. It's the `v8::String` based lookups and sets you need to be wary of. Remember it's cheap to cross the boundary, so if you'd like to have a method return a complex object, actually pass to the method a factory function that takes the representative pieces and creates the object in JavaScript and then returns it to the native layer, which is then free to do whatever it needs to with that object.
function createObj(a, b, c) {
  return {
    foo: a,
    bar: b,
    baz: c,
  };
}

var myObj = module.do_something('foobarbaz', createObj);
//myObj.foo
//myObj.bar
//myObj.baz
# Don't throw First and foremost, do **NOT** use C++ exceptions, ever, at all, in your code. Node is compiled with `-fno-exceptions` which doesn't prevent you from using exceptions, but does change how things are cleaned up after C++ exceptions are hit. Do **NOT** use them. Just don't. Try to avoid `throw`ing exceptions from the native layer. While it's certainly possible to do so (and Node does so in places) it won't be as helpful as you might want it to be, mostly you won't really know the what and where of when something threw on the native side, just that it did. (You can always use very explicit messages and things like `__func__`, `__FILE__`, `__LINE__` to help in that regard ... eww) Chances are if you let something through the JavaScript side that is wrong for the native side you want to `assert` and die a horrible death, instead of soldiering on only to have arbitrary memory corruption later. Remember, we're here to write JavaScript, don't be afraid of it. # Handle wrapping As an extension of the "JavaScript for consumers" section, consider the following pattern:
var assert = require('assert');
var binding = require('bindings')('mylib');

function Wrapper(opts) {
  if(!(this instanceof Wrapper))
    return new Wrapper(opts);

  assert(opts.foo);
  assert(opts.bar);

  this._handle = binding.libraryInit(opts.foo, opts.bar);
}

Wrapper.prototype.doSomething = function(baz) {
  assert(baz);

  binding.doSomething(this._handle, baz);
};

module.exports = Wrapper;
The idea here being that you have some sort of resource handle that needs to be reused in subsequent calls to your binding. Again, this is perfectly doable in C/C++ by following the `node::ObjectWrap` pattern, or implementing something similar yourself, but that's sacrificing most of what the JIT can provide you. In this model we can do more of the validation in JavaScript before sending it to C/C++ and potentially crashing. If you need to handle finalization of the handle after it goes out of scope in JavaScript, you should make the handle a `v8::Persistent` and then call [MakeWeak](http://izs.me/v8-docs/classv8_1_1Persistent.html#ab04609812113450bece2640ad0b27658) to define a callback that will be triggered when the GC is about to collect your object. # Use bindings Please oh please, use [bindings](http://npm.im/bindings). This simple library takes care of the naming and pathing changes that may occur from various configurations and versions of Node. Most importantly if I've compiled a debug version of Node, I'll actually build and run a debug version of your module and may be able to help figure out what might be going wrong. # Know your tools Do not be afraid to compile Node with `./configure --debug` and get your hands dirty with `mdb` or `gdb` to figure out just what is causing your module to crash. There are lots of resources out there to help you with that, but often just getting the stack trace from a core file will tell you quite a lot about what you've done wrong. # TL;DR Write more JavaScript and less C/C++ # TL;DR P2 * This code can't be optimized anymore than the compiler being used * Make sure consumers are getting JavaScript functions * It's cheaper than you think to call between JavaScript and C/C++ * Do **NOT** mutate objects in C/C++ * Avoid exceptions in C/C++ whenever possible * Define your wrapper classes in JavaScript * Use the [bindings](http://npm.im/bindings) module * Don't be afraid to debug

Posted in 

blog comments powered by Disqus