writing native bindings to node.js in c++
DESCRIPTION
Talk given at JSFoo Pune 2012http://jsfoo.in/pune2012/scheduleTRANSCRIPT
![Page 1: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/1.jpg)
Build the C linkNikhil Marathe
![Page 2: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/2.jpg)
Introduction
• V8 is a powerful, fast JavaScript engine
• It is self contained and easy to embed
JS is the new Lua?
• node.js is a thin wrapper around V8 and evented I/O (libuv)
![Page 3: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/3.jpg)
Follow along
Slides and code
git clone git://github.com/nikhilm/jsfoo-pune-2012.git
![Page 4: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/4.jpg)
We want to
Use C/C++ libraries in node.js
Exchange data between C++ ⇔ JS
Do asynchronous I/O
![Page 5: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/5.jpg)
Getting started
#include <v8.h>#include <node.h>
using namespace v8;
extern "C" { static void Init(Handle<Object> target) { } NODE_MODULE(firststep, Init)}
![Page 6: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/6.jpg)
Build
# firststep/wscriptimport Options
def set_options(opt): opt.tool_options("compiler_cxx")
def configure(conf): conf.check_tool("compiler_cxx") conf.check_tool("node_addon")
def build(bld): obj = bld.new_task_gen("cxx", "shlib", "node_addon") obj.target = "firststep" obj.source = "firststep.cc"
![Page 7: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/7.jpg)
Run
$ node-waf configure build...'build' finished successfully (0.327s)
$ node
> require('./build/Release/firststep'){}
![Page 8: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/8.jpg)
Architecture
1 you will need http://bespin.cz/~ondras/html/index.html
![Page 9: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/9.jpg)
![Page 10: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/10.jpg)
Handles
• Think of them as smart pointers, GCed by V8
• Also encode scope (Use HandleScope to manage handles)
• Local - GCed as they go out of scope:
Local<String> name; // also Handle<...>
• Persistent - Must be manually disposed:
Persistent<String> globalVariable;
![Page 11: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/11.jpg)
Injecting primitives
#include <math.h>#include <v8.h>#include <node.h>#include <node_version.h>using namespace v8;
extern "C" { static void Init(Handle<Object> target) { target->Set(String::NewSymbol("pi"), Number::New(M_PI));
NODE_DEFINE_CONSTANT(target, NODE_MINOR_VERSION);
target->Set(String::New("name"), String::New("Nikhil")); } NODE_MODULE(primitives, Init)}
![Page 12: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/12.jpg)
Simple functions
exports.square = function(n) { return n * n;}
We want to do this in C++
![Page 13: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/13.jpg)
Simple functions
Registering with V8:
Handle<Value> Square(const Arguments &args)
static void Init(Handle<Object> target) { HandleScope scope;
Handle<FunctionTemplate> squareTpl = FunctionTemplate::New(Square);
target->Set(String::New("square"), squareTpl->GetFunction()); }
![Page 14: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/14.jpg)
Simple functions
Implementation:
Handle<Value> Square(const Arguments &args) { HandleScope scope;
int a = args[0]->Int32Value(); int sq = a * a;
return scope.Close(Integer::New(sq));}
explain scope.Close
![Page 15: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/15.jpg)
Templates
FunctionTemplate ???
FunctionTemplate::GetFunction square [Function]
FunctionTemplate::InstanceTemplate() What `this` would be in 'new square()'
FunctionTemplate::PrototypeTemplate() square.prototype
![Page 16: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/16.jpg)
Simple objects
exports.Inventory = function() { this.items = 257;}
// latervar iv = new Inventory();console.log(iv.items);
This is the classic object oriented JS style
![Page 17: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/17.jpg)
Simple objects
static void Init(Handle<Object> target) { HandleScope scope;
Handle<FunctionTemplate> inventoryTpl = FunctionTemplate::New(Inventory);
Handle<ObjectTemplate> instance = inventoryTpl->InstanceTemplate();
instance->Set(String::New("items"), Integer::New(257));
target->Set(String::NewSymbol("Inventory"), inventoryTpl->GetFunction()); }
Handle<Value> Inventory(const Arguments &args) { return args.This();
![Page 18: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/18.jpg)
}
![Page 19: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/19.jpg)
Methods
Inventory.prototype.addStock = function(newStock) { this.items += newStock;}
Inventory.prototype.ship = function(orders) { if (this.items < orders) throw Exception("Not enough items");
this.items -= orders}
![Page 20: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/20.jpg)
Methods
Registering prototype methods
// operating on inventoryTpl->PrototypeTemplate() NODE_SET_PROTOTYPE_METHOD(inventoryTpl, "addStock", AddStock); NODE_SET_PROTOTYPE_METHOD(inventoryTpl, "ship", Ship);
target->Set(String::NewSymbol("Inventory"), inventoryTpl->GetFunction());
![Page 21: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/21.jpg)
Methods
Accessing object properties
Handle<Value> AddStock(const Arguments &args) { HandleScope scope;
Handle<Object> This = args.This();
int items = This->Get(String::New("items"))->Uint32Value();
items += args[0]->Uint32Value();
This->Set(String::New("items"), Integer::New(items));
return Undefined();}
![Page 22: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/22.jpg)
Methods
Throwing an exception
Handle<Value> Ship(const Arguments &args) { HandleScope scope;
Handle<Object> This = args.This(); int items = This->Get(String::New("items"))->Uint32Value();
int orders = args[0]->Uint32Value();
if (items < orders) return ThrowException(String::New("Not enough items"));
This->Set(String::New("items"), Integer::New(items - orders));
return Undefined();}
![Page 23: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/23.jpg)
ObjectWrap
• Associate native C++ objects with JS objects
• Node specific class which manages garbage collection
• Stored internally in fields
![Page 24: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/24.jpg)
ObjectWrap
// native C++ classnamespace Library {class Inventory { Inventory(); void addStock(int); int ship(int); int getItems();
int items; // private};}
![Page 25: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/25.jpg)
ObjectWrap
Setting internal field count
Handle<ObjectTemplate> instance = inventoryTpl->InstanceTemplate();
instance->SetInternalFieldCount(1);
![Page 26: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/26.jpg)
ObjectWrap
Wrapping
namespace binding {class Inventory : public ObjectWrap {public: static Handle<Value> New(const Arguments &args) { Inventory *wrapper = new Inventory(); wrapper->Wrap(args.Holder()); return args.Holder(); }
![Page 27: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/27.jpg)
ObjectWrap
Unwrapping
static Handle<Value> Ship(const Arguments &args) { // extract Inventory *wrapper = Unwrap<Inventory>(args.Holder());
int orders = args[0]->Uint32Value(); int result = wrapper->inv->ship(orders);
if (result == -1) return ThrowException(String::New("Not enough items"));
return Undefined(); }
![Page 28: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/28.jpg)
Going Async
• The easiest way is to use uv_queue_work()
• Every async call requires a set of 3 functions
1. Set up and invoke uv_queue_work()
2. Do blocking task (run in separate thread)
3. Clean up (run in main thread)• Use a 'baton' to pass around data
• uv_request_t is used by libuv
• But it’s data field is important to store the batonitself
• Slightly cumbersome :(
![Page 29: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/29.jpg)
Going Async
var inv = new (require('./build/Release/async')).Inventory()
inv.reshelve(function() { console.log("Reshelving done");})console.log("After reshelve in source");for (var i = 1; i < 5; i++) setTimeout(function() { console.log("Tick"); }, i*1000);
![Page 30: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/30.jpg)
Going Async
The native blocking code (method of class Library::Inventory)
void reshelve() { sleep(5); }
![Page 31: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/31.jpg)
Going Async
The baton
struct ReshelveBaton { uv_work_t request; Persistent<Function> callback; Inventory *wrapper; // any other data that has to be sent to the callback // or for async processing.}
![Page 32: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/32.jpg)
Going Async
JS callback
static Handle<Value> Reshelve(const Arguments &args) { Inventory *wrapper = Unwrap<Inventory>(args.Holder());
Handle<Function> cb = Handle<Function>::Cast(args[0]);
ReshelveBaton *baton = new ReshelveBaton(); baton->request.data = baton; baton->callback = Persistent<Function>::New(cb); baton->wrapper = wrapper;
uv_queue_work(Loop(), &baton->request, ReshelveAsync, ReshelveAsyncAfter);
return Undefined(); }
![Page 33: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/33.jpg)
Going Async
Thread pool function
static void ReshelveAsync(uv_work_t *req) { // This runs in a separate thread // NO V8 interaction should be done ReshelveBaton *baton = static_cast<ReshelveBaton*>(req->data); // if you want to modify baton values // do synchronous work here baton->wrapper->inv->reshelve(); }
![Page 34: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/34.jpg)
Going Async
Clean up
static void ReshelveAsyncAfter(uv_work_t *req) { ReshelveBaton *baton = static_cast<ReshelveBaton*>(req->data);
Handle<Value> argv[] = { Null() }; // no error baton->callback->Call(Context::GetCurrent()->Global(), 1, argv);
baton->callback.Dispose(); delete baton; }
![Page 35: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/35.jpg)
Going Async
Output
After reshelve in sourceTickTickTickTickReshelving done
![Page 36: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/36.jpg)
Linking your library
Linking external libs in Waf:
def configure(conf): # ... # uses pkg-config conf.check_cfg(package='<pkg-config name>', args='--cflags --libs', uselib_store='ALIAS')
def build(bld): # ... obj.uselib = 'ALIAS'
![Page 37: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/37.jpg)
Holder vs This
• args.This() is always the this object passed in to the function
• args.Holder() runs up the prototype chain to the ‘right’object
• Signatures decide the ‘right’ object, automatically handledby NODE_PROTOTYPE_SET_METHOD
• Always use Holder() to be on the safe side
![Page 38: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/38.jpg)
Things I haven’t covered
• Accessors
• Per property accessors
• Indexed accessors ( object[5] )
• Named property accessors ( object.property )• Function Signatures and HasInstance for type safety
• Emitting events using new JS only EventEmitter
• Details of libuv
• Using V8 on its own
![Page 39: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/39.jpg)
You might want to look at
• https://github.com/weaver/uuidjs
• https://github.com/nikhilm/node-taglib
• https://github.com/pietern/hiredis-node
![Page 40: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/40.jpg)
End notes
Contact:
• @nikhilcutshort
Cover image by Munjal Savla (by-nc-sa)
![Page 41: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/41.jpg)
Extra material below
![Page 42: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/42.jpg)
Calling JS functions
var calljs = require("./build/Release/calljs")
var f = function() { console.log("This", this); console.log(arguments);}
calljs.apply(f, { cool: "dude" }, "one", 2, 3.14);
![Page 43: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/43.jpg)
Calling JS functions
Handle<Value> Apply(const Arguments &args) { HandleScope scope;
Handle<Function> func = Handle<Function>::Cast(args[0]); Handle<Object> receiver = args[1]->ToObject();
Handle<Value> *argv = new Handle<Value>[args.Length() - 2]; for (int i = 2; i < args.Length(); i++) argv[i-2] = args[i];
func->Call(receiver, args.Length()-2, argv);
delete argv; return Undefined();}
![Page 44: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/44.jpg)
Strings to-and-fro
v8::String -> C string
Handle<Value> Print(const Arguments &args) { HandleScope scope;
for (int i = 0; i < args.Length(); i++) { if (!args[i]->IsString()) continue;
// also String::AsciiValue String::Utf8Value val(args[i]); printf("%s ", *val); // <<<<<< } return Undefined();}
![Page 45: Writing native bindings to node.js in C++](https://reader034.vdocuments.mx/reader034/viewer/2022052315/554f4e91b4c905524c8b4c99/html5/thumbnails/45.jpg)
Strings to-and-fro
C string -> v8::String
Handle<Value> Read(const Arguments &args) { HandleScope scope;
char str[1024]; fgets(str, 1023, stdin);
Local<String> v8String = String::New(str, strlen(str)); return scope.Close(v8String);}