Build the C link          Nikhil Marathe
Introduction • V8 is a powerful, fast JavaScript engine • It is self contained and easy to embed     JS is the new Lua? • ...
Follow alongSlides and code   git clone git://github.com/nikhilm/jsfoo-pune-2012.git
We want toUse C/C++ libraries in node.jsExchange data between C++ ⇔ JSDo asynchronous I/O
Getting started#include <v8.h>#include <node.h>using namespace v8;extern "C" {    static void Init(Handle<Object> target) ...
Build# firststep/wscriptimport Optionsdef set_options(opt):    opt.tool_options("compiler_cxx")def configure(conf):    con...
Run$ node-waf configure build...build finished successfully (0.327s)$ node> require(./build/Release/firststep){}
Architecture1          you will need http://bespin.cz/~ondras/html/index.html
Handles • Think of them as smart pointers, GCed by V8 • Also encode scope (Use HandleScope to manage handles) • Local - GC...
Injecting primitives#include <math.h>#include <v8.h>#include <node.h>#include <node_version.h>using namespace v8;extern "C...
Simple functions exports.square = function(n) {     return n * n; }We want to do this in C++
Simple functionsRegistering with V8: Handle<Value> Square(const Arguments &args) static void Init(Handle<Object> target) {...
Simple functionsImplementation: Handle<Value> Square(const Arguments &args) {     HandleScope scope;     int a = args[0]->...
TemplatesFunctionTemplate                        ???FunctionTemplate::GetFunction           square [Function]FunctionTempl...
Simple objects exports.Inventory = function() {     this.items = 257; } // later var iv = new Inventory(); console.log(iv....
Simple objectsstatic void Init(Handle<Object> target) {        HandleScope scope;        Handle<FunctionTemplate> inventor...
}
MethodsInventory.prototype.addStock = function(newStock) {    this.items += newStock;}Inventory.prototype.ship = function(...
MethodsRegistering prototype methods // operating on inventoryTpl->PrototypeTemplate()         NODE_SET_PROTOTYPE_METHOD(i...
MethodsAccessing object propertiesHandle<Value> AddStock(const Arguments &args) {    HandleScope scope;    Handle<Object> ...
MethodsThrowing an exceptionHandle<Value> Ship(const Arguments &args) {    HandleScope scope;    Handle<Object> This = arg...
ObjectWrap • Associate native C++ objects with JS objects • Node specific class which manages garbage collection • Stored ...
ObjectWrap// native C++ classnamespace Library {class Inventory {    Inventory();    void addStock(int);    int ship(int);...
ObjectWrapSetting internal field count Handle<ObjectTemplate> instance =             inventoryTpl->InstanceTemplate();    ...
ObjectWrapWrappingnamespace binding {class Inventory : public ObjectWrap {public:    static Handle<Value> New(const Argume...
ObjectWrapUnwrappingstatic Handle<Value> Ship(const Arguments &args) {        // extract        Inventory *wrapper = Unwra...
Going Async • The easiest way is to use uv_queue_work() • Every async call requires a set of 3 functions       1. Set up a...
Going Asyncvar inv = new (require(./build/Release/async)).Inventory()inv.reshelve(function() {    console.log("Reshelving ...
Going AsyncThe native blocking code (method of class Library::Inventory) void reshelve() {         sleep(5);     }
Going AsyncThe batonstruct ReshelveBaton {    uv_work_t request;    Persistent<Function> callback;    Inventory *wrapper; ...
Going AsyncJS callbackstatic Handle<Value> Reshelve(const Arguments &args) {        Inventory *wrapper = Unwrap<Inventory>...
Going AsyncThread pool function static void ReshelveAsync(uv_work_t *req) {         // This runs in a separate thread     ...
Going AsyncClean upstatic void ReshelveAsyncAfter(uv_work_t *req) {        ReshelveBaton *baton =            static_cast<R...
Going AsyncOutputAfter reshelve in sourceTickTickTickTickReshelving done
Linking your libraryLinking external libs in Waf:def configure(conf):  # ...  # uses pkg-config  conf.check_cfg(package=<p...
Holder vs This • args.This() is always the this object passed in to the function • args.Holder() runs up the prototype cha...
Things I haven’t covered • Accessors        • Per property accessors        • Indexed accessors ( object[5] )        • Nam...
You might want to look at • https://github.com/weaver/uuidjs • https://github.com/nikhilm/node-taglib • https://github.com...
End notesContact:  • @nikhilcutshort  • nsm.nikhil@gmail.comCover image by Munjal Savla (by-nc-sa)
Extra material below
Calling JS functionsvar calljs = require("./build/Release/calljs")var f = function() {    console.log("This", this);    co...
Calling JS functionsHandle<Value> Apply(const Arguments &args) {    HandleScope scope;    Handle<Function> func = Handle<F...
Strings to-and-frov8::String -> C string Handle<Value> Print(const Arguments &args) {     HandleScope scope;     for (int ...
Strings to-and-froC string -> v8::StringHandle<Value> Read(const Arguments &args) {    HandleScope scope;     char str[102...
Writing native bindings to node.js in C++
Upcoming SlideShare
Loading in...5
×

Writing native bindings to node.js in C++

41,052

Published on

Talk given at JSFoo Pune 2012
http://jsfoo.in/pune2012/schedule

Published in: Technology
4 Comments
54 Likes
Statistics
Notes
No Downloads
Views
Total Views
41,052
On Slideshare
0
From Embeds
0
Number of Embeds
5
Actions
Shares
0
Downloads
332
Comments
4
Likes
54
Embeds 0
No embeds

No notes for slide

Writing native bindings to node.js in C++

  1. 1. Build the C link Nikhil Marathe
  2. 2. 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)
  3. 3. Follow alongSlides and code git clone git://github.com/nikhilm/jsfoo-pune-2012.git
  4. 4. We want toUse C/C++ libraries in node.jsExchange data between C++ ⇔ JSDo asynchronous I/O
  5. 5. Getting started#include <v8.h>#include <node.h>using namespace v8;extern "C" { static void Init(Handle<Object> target) { } NODE_MODULE(firststep, Init)}
  6. 6. Build# firststep/wscriptimport Optionsdef 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"
  7. 7. Run$ node-waf configure build...build finished successfully (0.327s)$ node> require(./build/Release/firststep){}
  8. 8. Architecture1 you will need http://bespin.cz/~ondras/html/index.html
  9. 9. 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;
  10. 10. 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)}
  11. 11. Simple functions exports.square = function(n) { return n * n; }We want to do this in C++
  12. 12. Simple functionsRegistering 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()); }
  13. 13. Simple functionsImplementation: 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
  14. 14. TemplatesFunctionTemplate ???FunctionTemplate::GetFunction square [Function]FunctionTemplate::InstanceTemplate() What `this` would be in new square()FunctionTemplate::PrototypeTemplate() square.prototype
  15. 15. Simple objects exports.Inventory = function() { this.items = 257; } // later var iv = new Inventory(); console.log(iv.items);This is the classic object oriented JS style
  16. 16. Simple objectsstatic 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();
  17. 17. }
  18. 18. MethodsInventory.prototype.addStock = function(newStock) { this.items += newStock;}Inventory.prototype.ship = function(orders) { if (this.items < orders) throw Exception("Not enough items"); this.items -= orders}
  19. 19. MethodsRegistering 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());
  20. 20. MethodsAccessing object propertiesHandle<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();}
  21. 21. MethodsThrowing an exceptionHandle<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();}
  22. 22. ObjectWrap • Associate native C++ objects with JS objects • Node specific class which manages garbage collection • Stored internally in fields
  23. 23. ObjectWrap// native C++ classnamespace Library {class Inventory { Inventory(); void addStock(int); int ship(int); int getItems(); int items; // private};}
  24. 24. ObjectWrapSetting internal field count Handle<ObjectTemplate> instance = inventoryTpl->InstanceTemplate(); instance->SetInternalFieldCount(1);
  25. 25. ObjectWrapWrappingnamespace binding {class Inventory : public ObjectWrap {public: static Handle<Value> New(const Arguments &args) { Inventory *wrapper = new Inventory(); wrapper->Wrap(args.Holder()); return args.Holder(); }
  26. 26. ObjectWrapUnwrappingstatic 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(); }
  27. 27. 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 baton itself • Slightly cumbersome :(
  28. 28. Going Asyncvar 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);
  29. 29. Going AsyncThe native blocking code (method of class Library::Inventory) void reshelve() { sleep(5); }
  30. 30. Going AsyncThe batonstruct 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.}
  31. 31. Going AsyncJS callbackstatic 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(); }
  32. 32. Going AsyncThread 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(); }
  33. 33. Going AsyncClean upstatic 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; }
  34. 34. Going AsyncOutputAfter reshelve in sourceTickTickTickTickReshelving done
  35. 35. Linking your libraryLinking 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
  36. 36. 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 handled by NODE_PROTOTYPE_SET_METHOD • Always use Holder() to be on the safe side
  37. 37. 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
  38. 38. You might want to look at • https://github.com/weaver/uuidjs • https://github.com/nikhilm/node-taglib • https://github.com/pietern/hiredis-node
  39. 39. End notesContact: • @nikhilcutshort • nsm.nikhil@gmail.comCover image by Munjal Savla (by-nc-sa)
  40. 40. Extra material below
  41. 41. Calling JS functionsvar calljs = require("./build/Release/calljs")var f = function() { console.log("This", this); console.log(arguments);}calljs.apply(f, { cool: "dude" }, "one", 2, 3.14);
  42. 42. Calling JS functionsHandle<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();}
  43. 43. Strings to-and-frov8::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(); }
  44. 44. Strings to-and-froC string -> v8::StringHandle<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);}
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×