node.js/io.js native c++ addons

56
Node.js/io.js Native C++ Addons Chris Barber NodeMN Sept 8, 2015

Upload: chris-barber

Post on 13-Apr-2017

3.782 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Node.js/io.js Native C++ Addons

Node.js/io.jsNative C++ Addons

Chris BarberNodeMN Sept 8, 2015

Page 2: Node.js/io.js Native C++ Addons

About Me• Lead Software Engineer at

Appcelerator• JavaScript hacker since 2000• Open source

o Titaniumo Dojo Toolkit

• @cb1kenobio https://github.com/cb1kenobio https://twitter.com/cb1kenobi

Page 3: Node.js/io.js Native C++ Addons

My Projects• node-ios-device

o https://www.npmjs.com/package/node-ios-deviceo Talks to Apple’s MobileDevice framework to query iOS devices, install

apps, and tail syslog• lcdstats

o https://www.npmjs.com/package/lcdstatso Prints system info to LCD screen using USB

Page 4: Node.js/io.js Native C++ Addons

What is a C++ Addon?

Page 5: Node.js/io.js Native C++ Addons

What is a C++ Addon?• Dynamically linked shared object (.so, .dll)• Direct access to V8 APIs• Expose C++ functions to JavaScript

Page 6: Node.js/io.js Native C++ Addons

Getting Started• Python 2.7

o Python 3 won't worko Needed by GYP

• C++ toolchaino g++, makeo Xcode + CLI toolso Visual Studio

Page 7: Node.js/io.js Native C++ Addons

Example Addon

Page 8: Node.js/io.js Native C++ Addons

Example Addon• example.cpp

o Your C++ file• binding.gyp

o GYP file that says what and how your C++ should be compiled• package.json

o “gypfile”: trueo Tells NPM to run node-gyp

Page 9: Node.js/io.js Native C++ Addons

example.cpp#include <node.h>

using namespace v8;

void helloMethod(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));}

void init(Handle<Object> exports) { NODE_SET_METHOD(exports, "hello", helloMethod);}

NODE_MODULE(example, init)

Page 10: Node.js/io.js Native C++ Addons

binding.gyp{ "targets": [ { "target_name": "example", "sources": [ "example.cpp" ] } ]}

Page 11: Node.js/io.js Native C++ Addons

package.json{ "name": "example", "version": "1.0.0", "description": "C++ addon example", "gypfile": true}

Page 12: Node.js/io.js Native C++ Addons

Build & Run$ npm build .

> [email protected] install /Users/chris/projects/nodemn/example> node-gyp rebuild

CXX(target) Release/obj.target/example/example.o SOLINK_MODULE(target) Release/example.node

$ node> var example = require('./build/Release/example.node')undefined> example.hello()'world'

$ node –vv0.12.7

Page 13: Node.js/io.js Native C++ Addons

Build System

Page 14: Node.js/io.js Native C++ Addons

GYP• Generate Your Projects• https://code.google.com/p/gyp/• Written in Python (requires 2.7)• Generates projects:

o Makefiles, Xcode, Visual Studio, etc• .gyp file

o JSON + comments + double or single quotes + trailing commas• Features:

o Variables, includes, defaults, targets, dependencies, conditions, actions• Use to be used for V8 and Chrome

o Now replaced by gno V8 still ships a GYP file

Page 15: Node.js/io.js Native C++ Addons

node-gyp• https://github.com/nodejs/node-gyp• Node.js-based CLI tool that wraps GYP and builds

your C++ addons• Bundled with NPM

Page 16: Node.js/io.js Native C++ Addons

Building addons• NPM way:

$ npm build .

• node-gyp way:$ sudo npm install -g node-gyp

$ node-gyp configure

$ node-gyp build

Page 17: Node.js/io.js Native C++ Addons

node-gyp configure• Downloads Node dev files

o Stored in ~/.node-gyp• Invokes GYP

o Creates build project files

Page 18: Node.js/io.js Native C++ Addons

Node.js Versions

Page 19: Node.js/io.js Native C++ Addons

Module API VersionsNode.js API Version0.8.x 10.9.0 - 0.9.8 100.9.9 110.10.x 110.11.0 - 0.11.7 120.11.8 - 0.11.10 130.11.11 - 0.11.16 140.12.0 - 0.12.7 144.0.0 46

io.js API Version1.0.x 421.1.0 – 1.8.4 432.x 443.x 45

Page 20: Node.js/io.js Native C++ Addons

Building$ node –vv0.10.40

$ npm build .> [email protected] install /Users/chris/projects/nodemn/example> node-gyp rebuild

CXX(target) Release/obj.target/example/example.o../example.cpp:5:24: error: unknown type name 'FunctionCallbackInfo'void helloMethod(const FunctionCallbackInfo<Value>& args) { ^../example.cpp:5:44: error: expected ')'void helloMethod(const FunctionCallbackInfo<Value>& args) { ^../example.cpp:5:17: note: to match this '('void helloMethod(const FunctionCallbackInfo<Value>& args) { ^../example.cpp:7:15: error: no matching constructor for initialization of 'v8::HandleScope' HandleScope scope(isolate); ^ ~~~~~~~/Users/chris/.node-gyp/0.10.40/deps/v8/include/v8.h:473:3: note: candidate constructor not viable: no known conversion from 'v8::Isolate *' to 'const v8::HandleScope' for 1st argument HandleScope(const HandleScope&); ^/Users/chris/.node-gyp/0.10.40/deps/v8/include/v8.h:448:3: note: candidate constructor not viable: requires 0 arguments, but 1 was provided HandleScope(); ^../example.cpp:8:3: error: use of undeclared identifier 'args' args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world")); ^../example.cpp:8:37: error: no member named 'NewFromUtf8' in 'v8::String' args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world")); ~~~~~~~~^5 errors generated.make: *** [Release/obj.target/example/example.o] Error 1

Page 21: Node.js/io.js Native C++ Addons

What Happened?• Node.js 0.11 (unstable) introduced V8 3.17.13.0

o FYI: Node.js 4.0.0 uses V8 4.5.103.30• Major breaking APIs• Introduced "isolates"• V8 continues to change API

Page 22: Node.js/io.js Native C++ Addons

Node.js 0.12 vs 0.10// Node.js 0.12

#include <node.h>

using namespace v8;

void helloMethod(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); args.GetReturnValue().Set( String::NewFromUtf8(isolate, "world") );}

void init(Handle<Object> exports) { NODE_SET_METHOD(exports, "hello", helloMethod);}

NODE_MODULE(hello, init)

// Node.js 0.10

#include <node.h>#include <v8.h>

using namespace v8;

Handle<Value> helloMethod(const Arguments& args) { HandleScope scope; return scope.Close(String::New("world"));}

void init(Handle<Object> exports) { exports->Set(String::NewSymbol("hello"), FunctionTemplate::New(helloMethod)->GetFunction());}

NODE_MODULE(hello, init)

Page 23: Node.js/io.js Native C++ Addons

nan to the rescue

Page 24: Node.js/io.js Native C++ Addons

nan• Native abstractions for Node.js• Preprocessor macro magic• https://www.npmjs.com/package/nan

Page 25: Node.js/io.js Native C++ Addons

Install$ npm install nan --save

Page 26: Node.js/io.js Native C++ Addons

example2.cpp#include <nan.h>#include <node.h>

using namespace v8;

NAN_METHOD(helloMethod) { info.GetReturnValue().Set(Nan::New("world").ToLocalChecked());}

NAN_MODULE_INIT(init) { Nan::Set(target, Nan::New("hello").ToLocalChecked(), Nan::GetFunction(Nan::New<FunctionTemplate>(helloMethod)).ToLocalChecked() );}

NODE_MODULE(example2, init)

Page 27: Node.js/io.js Native C++ Addons

binding.gyp{ 'targets': [ { 'target_name': 'example2', 'include_dirs': [ '<!(node -e "require(\'nan\')")' ], 'sources': [ 'example2.cpp' ] } ]}

Page 28: Node.js/io.js Native C++ Addons

Build & Run$ node –v0.10.40

$ npm build .

> [email protected] install /Users/chris/projects/nodemn/example2> node-gyp rebuild

CXX(target) Release/obj.target/example2/example2.o SOLINK_MODULE(target) Release/example2.node

$ node> var example2 = require('./build/Release/example2.node')undefined> example2.hello()'world'

Page 29: Node.js/io.js Native C++ Addons

Build & Run$ node –v0.12.7

$ node> var example2 = require('./build/Release/example2.node')Error: Module did not self-register. at Error (native) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Module.require (module.js:365:17) at require (module.js:384:17) at repl:1:16 at REPLServer.defaultEval (repl.js:132:27) at bound (domain.js:254:14) at REPLServer.runBound [as eval] (domain.js:267:12) at REPLServer.<anonymous> (repl.js:279:12)

Page 30: Node.js/io.js Native C++ Addons

Why?• example2.node is compiled for Node.js 0.10.40

o API Version 11• Node.js 0.12 is API Version 14

Page 31: Node.js/io.js Native C++ Addons

Addon Compatibility• API Version• Node.js version

o 0.11 -> 0.12• Platform (OS X, Linux, Windows)• Architecture (32 vs 64-bit)

Page 32: Node.js/io.js Native C++ Addons

Solutions• Perfect solution?

o Doesn't exist today• Pre-build binaries in NPM package

o Pros:• Users always have a compatible addon ready to go• Users don't have to have C++ toolchain installed• Redistributable without needed Internet connection

o Cons:• Continually larger and larger package file size• Need to publish new binary for every new Node.js/io.js version• Need to manage version paths yourself

• node-pre-gypo Pros:

• Dynamically downloads binary at runtime; fallback to local build• Users don't have to have C++ toolchain installed unless required version does not exist• Small package size; no binaries in NPM• Automatically manages version paths

o Cons:• Need to publish new binary for every new Node.js/io.js version• Users either need an Internet connection or C++ toolchain

Page 33: Node.js/io.js Native C++ Addons

Only a partial solution• New Node.js/io.js releases all the time• Neglected addons• Some environments possibly untested• No way to tell NPM what platforms the addon is

targetingo NPM just tries to build it

Page 34: Node.js/io.js Native C++ Addons

State of C++ Addons

Page 35: Node.js/io.js Native C++ Addons

August 2014• 90,984 packages• 66,775 on Github• 860 are C++ addons

o 1.3% of all NPM packages that are on Github are C++ addons

0.10.30 0.11.13OS XLinuxWindows

473 (55%) 126 (14.7%)417 (48.5%) 129 (15%)295 (34.3%) 111 (12.9%)

Page 36: Node.js/io.js Native C++ Addons

September 2015• 193,225 packages• 141,953 on Github• 1,241 are C++ addons

o 0.87% of all NPM packages that are on Github are C++ addons• ~30% of all NPM packages depend on a C++

addono https://medium.com/node-js-javascript/4-0-is-the-new-1-0-

386597a3436d• Note: some C++ addons switched to pure JS impl0.10.40 0.12.7 4.0.0

OS XDidn't have time to run tests on Linux & Windows

729 (58.7%) 513 (41.3%) 237 (19.1%)

Page 37: Node.js/io.js Native C++ Addons

Addon State SummaryAugust 2014 September

2015Growth

# packages 90,984 193,225 2.12x# on Github 66,775 141,953 2.13x# C++ addons

860 (1.3%) 1,241 (0.87%) 1.44x

August 2014 September 20150.10.30 0.11.13 0.10.40 0.12.7 4.0.0

OS X 473 (55%) 126 (14.7%)

729 (58.7%)

513 (41.3%)

237 (19.1%)

Linux 417 (48.5%)

129 (15%) ? ? ?

Windows 295 (34.3%)

111 (12.9%)

? ? ?

Page 38: Node.js/io.js Native C++ Addons

Back to some code!

Page 39: Node.js/io.js Native C++ Addons

ObjectWrapExpose C++ Object to JS

Page 40: Node.js/io.js Native C++ Addons

example3.cpp#include <nan.h>

using namespace v8;

class MyObject : public Nan::ObjectWrap {public: static NAN_MODULE_INIT(Init) { Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(MyObject::New); tpl->SetClassName(Nan::New("MyObject").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1);

SetPrototypeMethod(tpl, "hello", Hello);

Nan::Set(target, Nan::New("MyObject").ToLocalChecked(), tpl->GetFunction()); }

protected: static NAN_METHOD(New) { MyObject *obj = new MyObject(); obj->Wrap(info.This()); info.GetReturnValue().Set(info.This()); }

static NAN_METHOD(Hello) { info.GetReturnValue().Set(Nan::New("world").ToLocalChecked()); }};

NAN_MODULE_INIT(init) { MyObject::Init(target);}

NODE_MODULE(example3, init)

Page 41: Node.js/io.js Native C++ Addons

Build & Run$ npm build .

> [email protected] install /Users/chris/projects/nodemn/example3> node-gyp rebuild

CXX(target) Release/obj.target/example3/example3.o SOLINK_MODULE(target) Release/example3.node

$ node> var example3 = require('./build/Release/example3.node')undefined> example3.MyObject

var obj = new example3.MyObject()undefined>

[Function: MyObject]>

obj.hello()'world'

Page 42: Node.js/io.js Native C++ Addons

C++ Addon Public API

Page 43: Node.js/io.js Native C++ Addons

C++ Addon Public API• Use an index.js to expose a high-level public API

o Things are generally easier in JavaScript• C++ addon implements low-level API

o Fancy APIs are a lot of work to build in C++

Page 44: Node.js/io.js Native C++ Addons

libuv

Page 45: Node.js/io.js Native C++ Addons

• Library for async I/O• Event loops• Thread pools• Thread synchronization• Async filesystem• Async networking• Child processes• Much more!

Page 46: Node.js/io.js Native C++ Addons

Case Study: deasync• https://github.com/abbr/deasync• Turns async functions into sync

var deasync = require('deasync');var cp = require('child_process');var exec = deasync(cp.exec);

console.log(exec('ls -la'));

Page 47: Node.js/io.js Native C++ Addons

Case Study: deasync// heavily simplified// original: https://github.com/abbr/deasync/blob/master/index.js

var binding = require(modPath); // path to deasync.node

function deasync(fn) { return function() { var done = false; fn.apply(this, args); module.exports.loopWhile(function () { return !done; }); };}

module.exports.loopWhile = function (pred) { while (pred()) { process._tickDomainCallback(); if (pred()) { binding.run(); } }};

Page 48: Node.js/io.js Native C++ Addons

Case Study: deasync#include <uv.h>#include <v8.h>#include <nan.h>

using namespace v8;

NAN_METHOD(Run) { Nan::HandleScope scope; uv_run(uv_default_loop(), UV_RUN_ONCE); info.GetReturnValue().Set(Nan::Undefined());}

static NAN_MODULE_INIT(init) { Nan::Set(target, Nan::New("run").ToLocalChecked(), Nan::GetFunction(Nan::New<FunctionTemplate>(Run)).ToLocalChecked() );}

NODE_MODULE(deasync, init)

Page 49: Node.js/io.js Native C++ Addons

Performance

Page 50: Node.js/io.js Native C++ Addons

Threading• Node.js is single threaded• Your C++ code can block your Node app• Use libuv for async tasks• uv_queue_work() makes async easy(ier)• No V8 access from worker threads• Threads can have their own event loops

Page 51: Node.js/io.js Native C++ Addons

C++ ⟷ JavaScript Bridge

#include <nan.h>#include <node.h>#include <math.h>

using namespace v8;

NAN_METHOD(crunch) { double n = floor(133.7 / 3.14159265359); info.GetReturnValue().Set(Nan::New<Number>(n));}

NAN_MODULE_INIT(init) { Nan::Set(target, Nan::New("crunch").ToLocalChecked(), Nan::GetFunction(Nan::New<FunctionTemplate>(crunch)) .ToLocalChecked() );}

NODE_MODULE(example4, init)

function jsCrunch() { return Math.floor(133.7 / Math.PI);}

var start = new Date;for (var i = 0; i < 1e8; i++) { jsCrunch();}var jsDelta = (new Date) - start;

var nativeCrunch = require( './build/Release/example4.node').crunch;

start = new Date;for (var i = 0; i < 1e8; i++) { nativeCrunch();}var nativeDelta = (new Date) - start;

console.log('JS took ' + jsDelta + 'ms');console.log('Native took ' + nativeDelta + 'ms');

Borrowed from http://kkaefer.github.io/node-cpp-modules

Page 52: Node.js/io.js Native C++ Addons

C++ ⟷ JavaScript Bridge$ node index.jsJS took 48msNative took 9397ms

Page 53: Node.js/io.js Native C++ Addons

JavaScript vs C++• V8 aggressively JITs JavaScript code

o Profiles and compiles it into native instructions• V8 is really fast

o Object allocation• Somethings you just can't do in JavaScript• Really depends on the use case and developer

skillo Leaving it up to you to decideo Perform your own benchmarks

Page 54: Node.js/io.js Native C++ Addons

Summary

Page 55: Node.js/io.js Native C++ Addons

Summary• Prefer a pure JavaScript solution over a C++

solutiono Unless A) you have to or B) you need performance

• Use nan• Highly consider using node-pre-gyp + continuous

build server• Use a JavaScript wrapper to implement public API• Don't block the main thread

o Use libuv to perform async operations

Page 56: Node.js/io.js Native C++ Addons

Questions?