Creating Node Addons

C/C++ extensions for fun and profit*

Marco Rogers

@polotek (twitter, github, irc, etc.)

*Typical results do not include fun or profit

Who am I?

What's an Addon?

Why would you wanna do this?

bcrypt

C library for encrypting data with blowfish.

    char* bcrypt_gensalt(u_int8_t log_rounds);
 
    char* bcrypt(const char* password, const char* salt);
    

Let's write an addon!

Code samples here https://github.com/polotek/node_addons_examples (forthcoming)

    // our API
    var BCrypt = require('bcrypt').BCrypt;

    var salt = '$2a$12$L/QwZlqLTII.Qx/p9kLt5.';
    var encrypter = new BCrypt();
    console.log( encrypter.encrypt('passw0rd', salt) );
    

How does it work?

v8 API

    // js
    function encrypt() {
        this; // could be anything

        var arg1 = "loser";
        if(arguments.length == 1)
            args[0];

        return "Hello, " + arg1;
    }
    

v8 API

    // C++
    Handle<Value> BCrypt::Encrypt(const Arguments& args) {
        args.This(); // of type Handle<Value>

        Handle<String> args1;
        if(args.Length() == 1) {
            args1 = args[0]->ToString();
        } else {
            args1 = String::New("loser");
        }

        return String::Concat( String::New("Hello, "), args1 );
    }
    

v8: Handles

Each reference to a js value in C/C++ is through a Handle. You need to "unwrap" Handles to get at the data types inside them.

A HandleScope is a temporary construct that keeps track of Handles and cleans them up when the stack pops off.

Always use a HandleScope at the beginning of your function.

v8: Handles

Local Handle - Cleaned up by scope after the function exits. Default handle type.

Persistent Handle - These allow you to keep objects around for as long as you need them.

v8: Handles

Probably the most important gotcha. Close the scope whenever you return a Handle from a function. Otherwise, the HandleScope will take it right out from under you.

    +HandleScope scope;

    return v8::Array::New();
    +return scope.Close(v8::Array::New());
    

Remember even innocent looking "literals" are actually Handles! Lots of commits that look like this.

node: ObjectWrap

        class BCrypt : public node::ObjectWrap {

          Handle<Value> BCrypt::Encrypt(const Arguments& args) {
            HandleScope scope;

            // retrieve your object from "this".
            BCrypt *bcrypt_obj = ObjectWrap::Unwrap<BCrypt>(args.This());
    

node: Classes and Utilities

        // C/C++
        class Connection : public node::EventEmitter {
          ...
          v8::Handle<v8::String> close = NODE_PSYMBOL("close");
          Emit(close_symbol, 0, NULL);
    
        // js
        var conn = new Connection();
        conn.on('close', function() { ... });
    

node: Classes and Utilities

Check out node/src/node.h for some nice macros and helper functions.

node: How to go from sync to async?

Kind of punting on this even though it's an important topic.

Node uses libeio to turn blocking file operations into non-blocking ones. It also has an API for doing this with any custom call. Push blocking library calls to a background thread. Isaac Schlueter has provided a simple example, node-async-simple. Complete but not comprehensive. Find out how you need to adapt this to your use case.

Don't try to access v8 from another thread. Unwrap everything to C/C++ types and only deal with those.

Lots we're not covering

Building with Waf

This process isn't fun. So...

Building with Waf

Just find a good one and learn from that. #winning

* libxmljs uses scons. Similar idea, another dependency. Not worth it.

Building with Waf

    # wscript
    srcdir = "."
    blddir = "build"

    def set_options(opt):
        opt.tool_options("compiler_cxx")
        opt.tool_options("compiler_cc")

    def configure(conf):
        conf.check_tool("compiler_cxx")
        conf.check_tool("compiler_cc")
        conf.check_tool("node_addon")

    def build(bld):
        bcryptnode = bld.new_task_gen("cxx", "shlib", "node_addon")
        bcryptnode.target = "bcrypt_lib"
        bcryptnode.source = "src/blowfish.cc src/bcrypt.cc src/bcrypt_node.cc"
    

Testing

Testing

        $> node --expose_gc addon_test.js

        // addon_test.js
        var libxmljs = require('libxmljs');
        var doc = libxmljs.parseXmlString('<pass><test/></pass>');
        console.log(doc.root().name()); // "pass" yay!
        // force GC, I hope nothing bad happened
        gc();
        console.log(doc.root().name()); // Segfaults, destruction and death
    

Testing

Use tools to ensure quality

Someone write some blog posts about these

npm

    $> npm help scripts

    // package.json
    {
      "name": "bcrypt",
      ...
      "scripts": {
        "install": "node-waf configure build",
        "test": "node-waf configure build && nodeunit test/"
      },
    

npm

Is using make more future-proof or just another dependency?

    // package.json
    {
      "name": "libxmljs",
      ...
      "scripts" :
      { "preinstall" : "make node",
        "test" : "make test"
      }
    
    # MakeFile
    node:
            node-waf configure build
    

Should you distribute the lib with your addon?

Resources

Questions?