├── .gitignore ├── README.md ├── basics ├── callback │ ├── binding.gyp │ ├── callback.cpp │ └── callback.js ├── cmath │ ├── binding.gyp │ ├── cmath.cpp │ └── cmath.js ├── helloworld0 │ ├── hello.js │ └── hello_module.js ├── helloworld1 │ ├── binding.gyp │ ├── hello.cpp │ └── hello.js └── whocalled │ ├── binding.gyp │ ├── hello.cpp │ └── hello.js ├── buffers ├── .DS_Store ├── .vscode │ └── .browse.VC.db ├── basic │ ├── binding.gyp │ ├── buffer_example.cpp │ ├── index.js │ └── package.json ├── images │ ├── .DS_Store │ ├── binding.gyp │ ├── index.js │ ├── lodepng.cpp │ ├── lodepng.h │ ├── package.json │ ├── png2bmp.cpp │ └── sample.png ├── post.md └── typed │ ├── binding.gyp │ ├── index.js │ ├── package.json │ └── typed_example.cpp ├── conversions ├── index.js ├── loose │ ├── binding.gyp │ └── loose_type_demo.cpp ├── package.json ├── readme.md └── strict │ ├── binding.gyp │ └── strict_type_demo.cpp ├── conversions_nan ├── basic_nan.cpp ├── basic_nan.js ├── binding.gyp └── package.json ├── lambda-cpp ├── addon │ ├── average.cpp │ ├── binding.gyp │ ├── output.txt │ └── package.json ├── average.zip └── lambda │ ├── index.js │ ├── package.json │ └── sample.js ├── objectwrap ├── binding.gyp ├── package.json ├── polynomial.cpp └── polynomial.js ├── objectwrap_nan ├── binding.gyp ├── npm-debug.log ├── package.json ├── polynomial.cpp └── polynomial.js ├── packaging ├── addlib │ ├── add.cpp │ ├── add.h │ ├── addlib.cpp │ ├── binding.gyp │ └── package.json ├── cpp11 │ ├── binding.gyp │ ├── hello11.cpp │ └── package.json ├── demo │ ├── index.js │ └── package.json ├── hello │ ├── binding.gyp │ ├── hello.cpp │ └── package.json └── hellonan │ ├── binding.gyp │ ├── hello_nan.cpp │ └── package.json ├── prebuilt ├── addon │ ├── binding.gyp │ ├── index.js │ ├── native-rt.cc │ ├── native-rt.h │ ├── native-rt_linux.cc │ ├── native-rt_mac.cc │ ├── native-rt_win.cc │ └── package.json └── example │ ├── index.js │ └── package.json ├── primes ├── binding.gyp ├── package.json ├── primes.cpp └── primes.js ├── quickstart ├── .vscode │ └── .browse.VC.db ├── addon │ ├── addon_source.cc │ ├── binding.gyp │ └── package.json ├── readme.md └── test │ ├── index.js │ └── package.json ├── rainfall ├── README.md ├── cpp │ ├── Makefile │ ├── binding.gyp │ ├── rainfall.cc │ ├── rainfall.h │ ├── rainfall_node.cc │ └── rainfall_test.cc ├── rainfall.js └── rainfall.json └── streaming ├── LICENSE.txt ├── dist ├── index.js ├── package.json └── streaming-worker.h ├── examples ├── accumulate │ ├── acc-emit.js │ ├── accumulate.cpp │ ├── accumulate.js │ ├── binding.gyp │ └── package.json ├── even_odd │ ├── binding.gyp │ ├── even_odd.js │ ├── even_odd_worker.cpp │ └── package.json ├── factorization │ ├── binding.gyp │ ├── factorization.cpp │ ├── factorization.js │ └── package.json ├── piping │ ├── package.json │ └── piping.js └── sensor_sim │ ├── binding.gyp │ ├── json.hpp │ ├── package.json │ ├── sensor_sim.cpp │ └── sensor_sim.js └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | cpp/rainfall.dSYM/* 3 | 4 | cpp/rainfall 5 | 6 | cpp/build 7 | 8 | conversions/loose/build 9 | conversions/strict/build 10 | *build* 11 | *node_modules* 12 | *.vscode/* 13 | buffers/basic/.vscode/* 14 | streaming/.vscode/* 15 | sample.bmp 16 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nodecpp-demo 2 | 3 | This repository contains full code listings that help guide you through developing Node.js addons. 4 | 5 | The content is fully explained on my blog - [blog.scottfrees.com](http://blog.scottfrees.com) and is the subject of my ebook - [C++ and Node.js Integration](http://scottfrees.com/ebooks/nodecpp). 6 | 7 | ## Basics 8 | Some simple "hello world" type programs, discussed in the first chapter of the [ebook](http://scottfrees.com/ebooks/nodecpp). 9 | 10 | ## Conversions 11 | Code "cheat sheet" for passing different data types between C++ and JavaScript. See [here](http://blog.scottfrees.com/type-conversions-from-javascript-to-c-in-v8) for associated blog post. This is also covered in Chapter 2 of the [ebook](http://scottfrees.com/ebooks/nodecpp). 12 | 13 | ## Conversions_NAN 14 | The same "cheat sheet" as Conversions, but using the NAN API rather than native V8. Covered in Chapter 6 of the [ebook](http://scottfrees.com/ebooks/nodecpp). 15 | 16 | ## Rainfall 17 | Contains a full example of using objects, arrays, and asynchronous callbacks to exchange data between JavaScript and C++ library for doing some (trivial) number crunching. This supports a 4-part blog series, which starts [here](http://blog.scottfrees.com/c-processing-from-node-js). Covered in Chapters 3 and 4 of the [ebook](http://scottfrees.com/ebooks/nodecpp). 18 | 19 | ## Primes 20 | A set of example addon functions demonstrating NAN's AsyncWorker and AsyncProgressWorker. Covered in Chapter 6 of the [ebook](http://scottfrees.com/ebooks/nodecpp). 21 | 22 | ## ObjectWrap 23 | An `ObjectWrap` example developing a C++ polynomial class. Covered in Chapter 5 of the [ebook](http://scottfrees.com/ebooks/nodecpp). 24 | 25 | ## ObjectWrap_NAN 26 | The same Polynomial class, but using NAN instead of direct Node.js API. Covered in Chapter 6 of the [ebook](http://scottfrees.com/ebooks/nodecpp). 27 | 28 | ## Streaming 29 | A complete SDK and set of examples for using NAN and asynchronous callbacks to stream data between C++ and JavaScript. This is the subject of Chapter 7 of the [ebook](http://scottfrees.com/ebooks/nodecpp). 30 | 31 | ## Buffers 32 | An example of using buffers to move data between JavaScript and a simple image processing C++ addon (converting PNG to BMP using [LodePNG](http://lodev.org/lodepng/)). This is covered in Appendix B of the [ebook](http://scottfrees.com/ebooks/nodecpp). 33 | 34 | ## Packaging 35 | A set of examples for building and distributing reusable addons. Covered in Chapter 8 of the [ebook](http://scottfrees.com/ebooks/nodecpp). 36 | -------------------------------------------------------------------------------- /basics/callback/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "callback", 5 | "sources": [ "callback.cpp" ] 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /basics/callback/callback.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace v8; 3 | 4 | 5 | void CallThis(const FunctionCallbackInfo& args) { 6 | Isolate* isolate = args.GetIsolate(); 7 | Local cb = Local::Cast(args[0]); 8 | cb->Call(Null(isolate), 0, nullptr); 9 | } 10 | void CallThisWithThis(const FunctionCallbackInfo& args) { 11 | Isolate* isolate = args.GetIsolate(); 12 | Local cb = Local::Cast(args[0]); 13 | Local argv[1] = {args[1]}; 14 | cb->Call(Null(isolate), 1, argv); 15 | } 16 | 17 | 18 | // Called when addon is require'd from JS 19 | void Init(Local exports) { 20 | NODE_SET_METHOD(exports, "callthis", CallThis); // we'll create Add in a moment... 21 | NODE_SET_METHOD(exports, "callthis_withthis", CallThisWithThis); 22 | } 23 | 24 | // The addon's name is cmath, this tells V8 what to call when it's require'd 25 | NODE_MODULE(callback, Init) -------------------------------------------------------------------------------- /basics/callback/callback.js: -------------------------------------------------------------------------------- 1 | const callback = require('./build/Release/callback'); 2 | 3 | var callme = function(message) { 4 | if ( message ) { 5 | console.log(message); 6 | } 7 | else { 8 | console.log("I've been called!"); 9 | } 10 | } 11 | 12 | callback.callthis(callme); 13 | callback.callthis_withthis(callme, "This is an important message"); -------------------------------------------------------------------------------- /basics/cmath/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "cmath", 5 | "sources": [ "cmath.cpp" ] 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /basics/cmath/cmath.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace v8; 3 | 4 | 5 | void Add(const FunctionCallbackInfo & args) { 6 | Isolate * isolate = args.GetIsolate(); 7 | 8 | if (args.Length() < 2) { 9 | return; 10 | } 11 | 12 | // value is created on the C++ runtime stack, not as part of 13 | // the JavaScript execution context... 14 | double value= args[0]->NumberValue() + args[1]->NumberValue(); 15 | 16 | // Now we create it in the JS execution context so we can return it 17 | Local num = Number::New(isolate, value); 18 | args.GetReturnValue().Set(num); 19 | } 20 | 21 | // Called when addon is require'd from JS 22 | void Init(Local exports) { 23 | NODE_SET_METHOD(exports, "add", Add); // we'll create Add in a moment... 24 | } 25 | 26 | // The addon's name is cmath, this tells V8 what to call when it's require'd 27 | NODE_MODULE(cmath, Init) -------------------------------------------------------------------------------- /basics/cmath/cmath.js: -------------------------------------------------------------------------------- 1 | const cmath = require('./build/Release/cmath'); 2 | const five = cmath.add(2, 3); 3 | console.log( '2 + 3 is ', five ); 4 | 5 | var u = cmath.add(1) 6 | 7 | console.log(u); 8 | console.log(u===undefined); -------------------------------------------------------------------------------- /basics/helloworld0/hello.js: -------------------------------------------------------------------------------- 1 | var hello = require("./hello_module"); 2 | console.log(hello.say_hello()); -------------------------------------------------------------------------------- /basics/helloworld0/hello_module.js: -------------------------------------------------------------------------------- 1 | exports.say_hello = function() { 2 | return "hello world"; 3 | } -------------------------------------------------------------------------------- /basics/helloworld1/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "hello_addon", 5 | "sources": [ "hello.cpp" ] 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /basics/helloworld1/hello.cpp: -------------------------------------------------------------------------------- 1 | // hello.cpp 2 | #include 3 | 4 | using namespace v8; 5 | 6 | void Method(const FunctionCallbackInfo& args) { 7 | Isolate* isolate = args.GetIsolate(); 8 | Local retval = String::NewFromUtf8(isolate, "world"); 9 | args.GetReturnValue().Set(retval); 10 | } 11 | 12 | void init(Local exports) { 13 | NODE_SET_METHOD(exports, "hello", Method); 14 | } 15 | 16 | NODE_MODULE(hello_addon, init) 17 | 18 | -------------------------------------------------------------------------------- /basics/helloworld1/hello.js: -------------------------------------------------------------------------------- 1 | // hello.js 2 | const addon = require('./build/Release/hello_addon'); 3 | 4 | console.log(addon.hello()); // 'world' -------------------------------------------------------------------------------- /basics/whocalled/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "who_addon", 5 | "sources": [ "hello.cpp" ] 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /basics/whocalled/hello.cpp: -------------------------------------------------------------------------------- 1 | // hello.cpp 2 | #include 3 | 4 | using namespace v8; 5 | 6 | void Method(const FunctionCallbackInfo& args) { 7 | args.GetReturnValue().Set(args.Holder()); 8 | } 9 | 10 | void init(Local exports) { 11 | NODE_SET_METHOD(exports, "hello", Method); 12 | } 13 | 14 | NODE_MODULE(hello_addon, init) 15 | 16 | -------------------------------------------------------------------------------- /basics/whocalled/hello.js: -------------------------------------------------------------------------------- 1 | // hello.js 2 | const addon = require('./build/Release/who_addon'); 3 | 4 | var me = { 5 | hello : addon.hello 6 | } 7 | 8 | var who = me.hello(); 9 | console.log(who); 10 | -------------------------------------------------------------------------------- /buffers/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freezer333/nodecpp-demo/9130c01631ba20048c3fffc3632eadcb94971724/buffers/.DS_Store -------------------------------------------------------------------------------- /buffers/.vscode/.browse.VC.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freezer333/nodecpp-demo/9130c01631ba20048c3fffc3632eadcb94971724/buffers/.vscode/.browse.VC.db -------------------------------------------------------------------------------- /buffers/basic/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "buffer_example", 5 | "sources": [ "buffer_example.cpp" ], 6 | "include_dirs" : [" 2 | using namespace Nan; 3 | using namespace v8; 4 | 5 | NAN_METHOD(rotate) { 6 | char* buffer = (char*) node::Buffer::Data(info[0]->ToObject()); 7 | size_t size = node::Buffer::Length(info[0]); 8 | unsigned int rot = info[1]->Uint32Value(); 9 | 10 | char * retval = new char[size]; 11 | for(unsigned int i = 0; i < size; i++ ) { 12 | retval[i] = buffer[i] - rot; 13 | buffer[i] += rot; 14 | } 15 | 16 | info.GetReturnValue().Set(Nan::NewBuffer(retval, size).ToLocalChecked()); 17 | } 18 | 19 | NAN_MODULE_INIT(Init) { 20 | Nan::Set(target, New("rotate").ToLocalChecked(), 21 | GetFunction(New(rotate)).ToLocalChecked()); 22 | } 23 | 24 | NODE_MODULE(buffer_example, Init) -------------------------------------------------------------------------------- /buffers/basic/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const addon = require('./build/Release/buffer_example'); 3 | 4 | const buffer = Buffer.from("ABC"); 5 | 6 | // synchronous, rotates each character by +13 7 | var result = addon.rotate(buffer, 13); 8 | 9 | console.log(buffer.toString('ascii')); 10 | console.log(result.toString('ascii')); 11 | 12 | -------------------------------------------------------------------------------- /buffers/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "buffer_example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "gypfile": true, 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "dependencies": { 10 | "nan": "*" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /buffers/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freezer333/nodecpp-demo/9130c01631ba20048c3fffc3632eadcb94971724/buffers/images/.DS_Store -------------------------------------------------------------------------------- /buffers/images/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "png2bmp", 5 | "sources": [ "png2bmp.cpp", "lodepng.cpp" ], 6 | "cflags": ["-Wall", "-Wextra", "-pedantic", "-ansi", "-O3"], 7 | "include_dirs" : [" 2 | #include 3 | #include 4 | #include 5 | #include "lodepng.h" 6 | 7 | using namespace std; 8 | using namespace Nan; 9 | using namespace v8; 10 | 11 | /* 12 | ALL LodePNG code in this file is adapted from lodepng's examples, found at the following URL: 13 | 14 | https://github.com/lvandeve/lodepng/blob/master/examples/example_bmp2png.cpp' 15 | */ 16 | 17 | 18 | //Input image must be RGB buffer (3 bytes per pixel), but you can easily make it 19 | //support RGBA input and output by changing the inputChannels and/or outputChannels 20 | //in the function to 4. 21 | void encodeBMP(std::vector& bmp, const unsigned char* image, int w, int h) 22 | { 23 | //3 bytes per pixel used for both input and output. 24 | int inputChannels = 3; 25 | int outputChannels = 3; 26 | 27 | //bytes 0-13 28 | bmp.push_back('B'); bmp.push_back('M'); //0: bfType 29 | bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); //2: bfSize; size not yet known for now, filled in later. 30 | bmp.push_back(0); bmp.push_back(0); //6: bfReserved1 31 | bmp.push_back(0); bmp.push_back(0); //8: bfReserved2 32 | bmp.push_back(54 % 256); bmp.push_back(54 / 256); bmp.push_back(0); bmp.push_back(0); //10: bfOffBits (54 header bytes) 33 | 34 | //bytes 14-53 35 | bmp.push_back(40); bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); //14: biSize 36 | bmp.push_back(w % 256); bmp.push_back(w / 256); bmp.push_back(0); bmp.push_back(0); //18: biWidth 37 | bmp.push_back(h % 256); bmp.push_back(h / 256); bmp.push_back(0); bmp.push_back(0); //22: biHeight 38 | bmp.push_back(1); bmp.push_back(0); //26: biPlanes 39 | bmp.push_back(outputChannels * 8); bmp.push_back(0); //28: biBitCount 40 | bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); //30: biCompression 41 | bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); //34: biSizeImage 42 | bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); //38: biXPelsPerMeter 43 | bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); //42: biYPelsPerMeter 44 | bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); //46: biClrUsed 45 | bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); bmp.push_back(0); //50: biClrImportant 46 | 47 | /* 48 | Convert the input RGBRGBRGB pixel buffer to the BMP pixel buffer format. There are 3 differences with the input buffer: 49 | -BMP stores the rows inversed, from bottom to top 50 | -BMP stores the color channels in BGR instead of RGB order 51 | -BMP requires each row to have a multiple of 4 bytes, so sometimes padding bytes are added between rows 52 | */ 53 | 54 | int imagerowbytes = outputChannels * w; 55 | imagerowbytes = imagerowbytes % 4 == 0 ? imagerowbytes : imagerowbytes + (4 - imagerowbytes % 4); //must be multiple of 4 56 | 57 | for(int y = h - 1; y >= 0; y--) //the rows are stored inversed in bmp 58 | { 59 | int c = 0; 60 | for(int x = 0; x < imagerowbytes; x++) 61 | { 62 | if(x < w * outputChannels) 63 | { 64 | int inc = c; 65 | //Convert RGB(A) into BGR(A) 66 | if(c == 0) inc = 2; 67 | else if(c == 2) inc = 0; 68 | bmp.push_back(image[inputChannels * (w * y + x / outputChannels) + inc]); 69 | } 70 | else bmp.push_back(0); 71 | c++; 72 | if(c >= outputChannels) c = 0; 73 | } 74 | } 75 | 76 | // Fill in the size 77 | bmp[2] = bmp.size() % 256; 78 | bmp[3] = (bmp.size() / 256) % 256; 79 | bmp[4] = (bmp.size() / 65536) % 256; 80 | bmp[5] = bmp.size() / 16777216; 81 | } 82 | 83 | 84 | bool do_convert(std::vector & input_data, std::vector & bmp) 85 | { 86 | std::vector image; //the raw pixels 87 | unsigned width, height; 88 | unsigned error = lodepng::decode(image, width, height, input_data, LCT_RGB, 8); 89 | if(error) { 90 | std::cout << "error " << error << ": " << lodepng_error_text(error) << std::endl; 91 | return false; 92 | } 93 | encodeBMP(bmp, &image[0], width, height); 94 | return true; 95 | } 96 | 97 | 98 | 99 | 100 | 101 | void buffer_delete_callback(char* data, void* the_vector) { 102 | delete reinterpret_cast *> (the_vector); 103 | } 104 | 105 | NAN_METHOD(GetBMP) { 106 | 107 | unsigned char*buffer = (unsigned char*) node::Buffer::Data(info[0]->ToObject()); 108 | unsigned int size = info[1]->Uint32Value(); 109 | 110 | std::vector png_data(buffer, buffer + size); 111 | 112 | // allocate the vector on the heap because we are building a buffer 113 | // out of it's data to return to Node - and don't want to allow 114 | // it to go out of scope until the buffer does (see buffer_delete_callback). 115 | std::vector * bmp = new vector(); 116 | 117 | if ( do_convert(png_data, *bmp)) { 118 | info.GetReturnValue().Set( 119 | NewBuffer((char *)bmp->data(), bmp->size(), buffer_delete_callback, bmp).ToLocalChecked()); 120 | } 121 | } 122 | 123 | 124 | class PngToBmpWorker : public AsyncWorker { 125 | public: 126 | PngToBmpWorker(Callback * callback, 127 | v8::Local &pngBuffer, int size) 128 | : AsyncWorker(callback) { 129 | unsigned char*buffer = (unsigned char*) node::Buffer::Data(pngBuffer); 130 | std::vector tmp(buffer, buffer + (unsigned int) size); 131 | png_data = tmp; 132 | } 133 | void Execute() { 134 | bmp = new vector(); 135 | do_convert(png_data, *bmp); 136 | } 137 | void HandleOKCallback () { 138 | Local bmpData = 139 | NewBuffer((char *)bmp->data(), 140 | bmp->size(), buffer_delete_callback, 141 | bmp).ToLocalChecked(); 142 | Local argv[] = { Nan::Null(), bmpData }; 143 | callback->Call(2, argv); 144 | } 145 | 146 | private: 147 | vector png_data; 148 | std::vector * bmp; 149 | }; 150 | 151 | NAN_METHOD(GetBMPAsync) { 152 | int size = To(info[1]).FromJust(); 153 | v8::Local pngBuffer = info[0]->ToObject(); 154 | Callback *callback = new Callback(info[2].As()); 155 | AsyncQueueWorker(new PngToBmpWorker(callback, pngBuffer , size)); 156 | } 157 | 158 | NAN_MODULE_INIT(Init) { 159 | 160 | Nan::Set(target, New("getBMP").ToLocalChecked(), 161 | GetFunction(New(GetBMP)).ToLocalChecked()); 162 | 163 | Nan::Set(target, New("getBMPAsync").ToLocalChecked(), 164 | GetFunction(New(GetBMPAsync)).ToLocalChecked()); 165 | } 166 | 167 | NODE_MODULE(basic_nan, Init) -------------------------------------------------------------------------------- /buffers/images/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freezer333/nodecpp-demo/9130c01631ba20048c3fffc3632eadcb94971724/buffers/images/sample.png -------------------------------------------------------------------------------- /buffers/post.md: -------------------------------------------------------------------------------- 1 | # Using Buffers to share data between Node.js and C++ 2 | One of the really nice things about developing with Node.js is the ability to move fairly seamlessly between JavaScript and native C++ code - thanks to the V8's addon API. The ability to move into C++ is sometimes driven by processing speed; but more often it's because we already have C++ code and we just want to be able to use it from JavaScript. We can categorize the different use cases for addons over (at least) two axes - (1) amount of processing time we'll spend in the C++ code, and (2) the amount of data flowing between C++ and JavaScript. 3 | 4 | ![CPU vs. Data quadrant](https://scottfrees.com/quadrant.png) 5 | 6 | Most articles discussing C++ addons for Node.js focus on the differences between the left and right quadrants. If you are in the left quadrants (short processing time), your addon can possibly be synchronous - meaning the C++ code that executes is run directly in the Node.js event loop when called. In this situation, your addon function blocks - the calling JavaScript code simply waits for the return value from the addon. In the right quadrants, you would almost certainly design the addon using the asynchronous pattern. In an asynchronous addon function, the calling JavaScript code returns immediately. The calling code passes a callback function to the addon, and the addon does it's work in a separate worker thread. This avoids locking up the Node.js event loop, as the addon function does not block. 7 | 8 | The difference between the top and bottom quadrants is often overlooked however - but can be just as important. This post is going to focus on this topic - and we'll wind up presenting Node.js Buffers as a powerful solution to the data problems we are about to describe. 9 | 10 | # V8 vs. C++ memory and data 11 | If you are new to writing native addons, one of the first things you must master is the differences between V8-owned data (which you **can** access from C++ addons) and normal C++ memory allocations. When we say "V8-owned", we are referring to the storage cells that hold JavaScript data. These storage cells are accessibly through V8's C++ API - but they aren't ordinary C++ variables, they can be accessed in only limited ways. While your addon *could* restrict itself to ONLY using V8 data, it's more than likely it will also create it's own variables - in plain old C++. These could be stack or heap variables, and of course are completely independent of V8. 12 | 13 | In JavaScript, primitives (numbers, strings, booleans, etc.) are *immutable* - storage cells associated with primitive JavaScript variables cannot be altered by a C++ addon. The primitive JavaScript variables can be reassigned to *new storage cells* created by C++ - but this means changing data always results in *new* memory allocation. In the upper quadrant (low data transfer), this really isn't a big deal - if you are designing an addon that doesn't have a lot of data exchange, then the overhead of all the new memory allocation in your addon probably doesn't mean much. As your addons moves closer to the lower quadrant however, the allocation / copying starts to cost you. For one, It costs you in terms of peak memory usage, and **it also costs you in performance**! The time cost of copying all this data between JavaScript (V8 storage cells) to C++ (and back) usually kills the performance benefits you might be getting from running C++ in the first place! For addons in the lower left quadrant (low processing, high data usage), the latency associated with data copying can push your addon towards the right - forcing you to consider an asynchronous design. 14 | 15 | # V8 memory and asynchronous addons 16 | In asynchronous addons, we execute the bulk of our C++ processing code a worker thread. If you are unfamiliar with asynchronous callbacks, you might want to checkout a few tutorials (like [here](http://blog.scottfrees.com/building-an-asynchronous-c-addon-for-node-js-using-nan) and [here](http://blog.scottfrees.com/c-processing-from-node-js-part-4-asynchronous-addons)). 17 | 18 | A central tenant of asynchronous addons is that *you can't access V8 (JavaScript) memory outside the event-loop's thread*. This leads us to our next problem - if we have lots of data, that data must be copied out of V8 memory and into your addon's native address space *from the event loop's thread*, before the worker thread starts. Likewise, any data produced or modified by the worker thread must be copied back into V8 by code executing in the event loop (in the callback). If you are interested in high throughput Node.js applications, you should hate spending lots of time in the event loop copying data! 19 | 20 | Creating copies for input and output for a C++ worker thread 21 | 22 | Ideally, we'd prefer a way to do this: 23 | 24 | Accessing V8 data directly from C++ worker thread 25 | 26 | # Buffers to the rescue 27 | So, we have two somewhat related problems. When working with synchronous addons, unless we aren't changing/producing data, it's likely we'll need to spend a lot of time moving our data between V8 storage cells and plain old C++ variables - which costs us. If we are working with asynchronous addons - where ideally we should spend as little time in the event loop as possible, we are still "up a creek" - since we *must* do our data copying in the event loop's thread due to V8's multi-threaded restrictions. This is where an often overlooked features of Node.js helps us with addon development - the `Buffer`. Quoting the [Node.js official documentation](https://nodejs.org/api/buffer.html), 28 | 29 | > Instances of the Buffer class are similar to arrays of integers but correspond to fixed-sized, raw memory allocations outside the V8 heap. 30 | 31 | This is exactly what we are looking for - because the data inside a Buffer is *not stored in a V8 storage cell*, it is not subject to the multi-threading rules of V8. This means we can interact with it **in place** from a C++ worker thread started by an asynchronous addon. 32 | 33 | ## How Buffers work 34 | Buffers store raw binary data, they are found in the Node.js API for reading files and other I/O devices. 35 | 36 | Borrowing again from some examples in the Node.js documentation, we can create unitialized buffers of a specified size, buffers pre-set with a specified value, buffers from arrays of bytes, and buffers from strings. 37 | 38 | ```js 39 | // buffer with size 10 bytes 40 | const buf1 = Buffer.alloc(10); 41 | 42 | // buffer filled with 1's (10 bytes) 43 | const buf2 = Buffer.alloc(10, 1); 44 | 45 | //buffer containing [0x1, 0x2, 0x3] 46 | const buf3 = Buffer.from([1, 2, 3]); 47 | 48 | // buffer containing ASCII bytes [0x74, 0x65, 0x73, 0x74]. 49 | const buf4 = Buffer.from('test'); 50 | 51 | // buffer containing bytes from a file 52 | const buf5 = fs.readFileSync("some file"); 53 | ``` 54 | 55 | Buffers can be turned back into traditional JavaScript data (strings) or written back out to files, databases, or other I/O devices. 56 | 57 | ## How to access Buffers in C++ 58 | When building an addon for Node.js, the best place to start is by making use of the NAN (Native Abstractions for Node.js) API rather than directly using the V8 API - which can be a moving target. There are many tutorials on the web for getting started with NAN addons - including [examples](https://github.com/nodejs/nan#example) in NAN's code base itself. I've written a bit about it [here](http://blog.scottfrees.com/building-an-asynchronous-c-addon-for-node-js-using-nan), and it's also covered in a lot of depth in my [ebook](https://scottfrees.com/ebooks/nodecpp/). While I'm going to assume you know the basics of addon development, lets take a look specifically at how `Buffers` are used. 59 | 60 | First lets see how an addon can access a Buffer sent to it from JavaScript. We'll start with a simple JS program that require's an addon that we'll create in a moment: 61 | 62 | ```js 63 | 'use strict'; 64 | // Requiring the addon that we'll build in a moment... 65 | const addon = require('./build/Release/buffer_example'); 66 | 67 | // Allocates memory holding ASCII "ABC" outside of V8. 68 | const buffer = Buffer.from("ABC"); 69 | 70 | // synchronous, rotates each character by +13 71 | addon.rotate(buffer, buffer.length, 13); 72 | 73 | console.log(buffer.toString('ascii')); 74 | ``` 75 | 76 | The expected output is "NOP", the ascii rotation by 13 of "ABC". Let's take a look the addon - it consists of three files (in the same directory, for simplicity): 77 | 78 | ```js 79 | // binding.gyp 80 | { 81 | "targets": [ 82 | { 83 | "target_name": "buffer_example", 84 | "sources": [ "buffer_example.cpp" ], 85 | "include_dirs" : [" 110 | using namespace Nan; 111 | using namespace v8; 112 | 113 | NAN_METHOD(rotate) { 114 | char* buffer = (char*) node::Buffer::Data(info[0]->ToObject()); 115 | unsigned int size = info[1]->Uint32Value(); 116 | unsigned int rot = info[2]->Uint32Value(); 117 | 118 | for(unsigned int i = 0; i < size; i++ ) { 119 | buffer[i] += rot; 120 | } 121 | } 122 | 123 | NAN_MODULE_INIT(Init) { 124 | Nan::Set(target, New("rotate").ToLocalChecked(), 125 | GetFunction(New(rotate)).ToLocalChecked()); 126 | } 127 | 128 | NODE_MODULE(buffer_example, Init) 129 | ``` 130 | 131 | The most interesting file is `buffer_example.cpp` - notice we've used `node::Buffer`'s `Data` method to convert the first parameter sent to the addon to an unsigned character array. This is now free for us to use in any way we see fit - in this case we just perform an ASCII rotation of the text. Notice there is no return value - the memory associated with the Buffer has been modified **in place**. 132 | 133 | We can build the addon by just typing `npm install` - the `package.json` tells npm to download NAN and build the addon using the `binding.gyp` file. Running it will give us the "NOP" output we expect. 134 | 135 | We can also create *new* buffers while inside the addon. Let's modify the rotate function to increment the input, but return another buffer containing the string resulting from a decrement operation: 136 | 137 | ```cpp 138 | NAN_METHOD(rotate) { 139 | char* buffer = (char*) node::Buffer::Data(info[0]->ToObject()); 140 | unsigned int size = info[1]->Uint32Value(); 141 | unsigned int rot = info[2]->Uint32Value(); 142 | 143 | char * retval = new char[size]; 144 | for(unsigned int i = 0; i < size; i++ ) { 145 | retval[i] = buffer[i] - rot; 146 | buffer[i] += rot; 147 | } 148 | 149 | info.GetReturnValue().Set(Nan::NewBuffer(retval, size).ToLocalChecked()); 150 | } 151 | ``` 152 | 153 | ```js 154 | var result = addon.rotate(buffer, buffer.length, 13); 155 | 156 | console.log(buffer.toString('ascii')); 157 | console.log(result.toString('ascii')); 158 | ``` 159 | 160 | Now the resulting buffer will contain '456'. Note the use of NAN's `NewBuffer` function, which wraps the dynamically allocated `retval` array in a Node buffer. Doing so *transfers ownership* of this memory to Node.js - the memory associated with `retval` will be reclaimed (by calling `free`) when the buffer goes out of scope in JavaScript. More on this issue later - as we don't always want to have this happen this way! 161 | 162 | You can find additional information about how NAN handles buffers [here](https://github.com/nodejs/nan/blob/master/doc/buffers.md). 163 | 164 | # Example: PNG and BMP Image Processing 165 | The example above is pretty basic - and not particularly exciting. Let's turn to a more practical example - image processing with C++. If you want to get the full source code for both the example above, and the image processing code below, you can head over to my `nodecpp-demo` repository at [https://github.com/freezer333/nodecpp-demo](https://github.com/freezer333/nodecpp-demo), the code is in the "buffers" directory. 166 | 167 | Image processing is a good candidate for C++ addons, as it can often be time consuming, CPU intensive, and some processing techniques have parallelism that C++ can exploit well. For the example we'll look at now, we'll simply convert png formatted data into bmp formatted data [1](#footnote1). There are a good number of existing, open source C++ libraries that can help us with this task, I'm going to use LodePNG as it is dependency free and quite simple to use. LodePNG can be found at [http://lodev.org/lodepng/](http://lodev.org/lodepng/), and it's source code is at [https://github.com/lvandeve/lodepng](https://github.com/lvandeve/lodepng). Many thanks to the developer, Lode Vandevenne for providing such an easy to use library! 168 | 169 | ## Setting up the addon 170 | For this addon, we'll create the following directory structure, which includes source code downloaded from [https://github.com/lvandeve/lodepng](https://github.com/lvandeve/lodepng), namely `lodepng.h` and `lodepng.cpp`. 171 | 172 | ``` 173 | /png2bmp 174 | | 175 | |--- binding.gyp 176 | |--- package.json 177 | |--- png2bmp.cpp # the addon 178 | |--- index.js # program to test the addon 179 | |--- sample.png # input (will be converted to bmp) 180 | |--- lodepng.h # from lodepng distribution 181 | |--- lodepng.cpp # From loadpng distribution 182 | ``` 183 | 184 | `lodepng.cpp` contains all the necessary code for doing image processing, and I will not discuss it's working in detail. In addition, the lodepng distribution contains sample code that allows you to specifically convert between png and bmp - I've adapted it slightly, and will put it in the addon source code file `png2bmp.cpp` which we will take a look at shortly. Let's first look at what the actual JavaScript program looks like though - before diving into the addon code itself: 185 | 186 | ```js 187 | 'use strict'; 188 | const fs = require('fs'); 189 | const path = require('path'); 190 | const png2bmp = require('./build/Release/png2bmp'); 191 | 192 | const png_file = process.argv[2]; 193 | const bmp_file = path.basename(png_file, '.png') + ".bmp"; 194 | const png_buffer = fs.readFileSync(png_file); 195 | 196 | const bmp_buffer = png2bmp.getBMP(png_buffer, png_buffer.length); 197 | fs.writeFileSync(bmp_file, bmp_buffer); 198 | ``` 199 | 200 | The program uses accepts a filename for a png image as a command line option. It calls an addon function `getBMP` which accepts a buffer containing the png file and it's length. This addon is *synchronous* we'll take a look at the asynchronous version later on. 201 | 202 | Here's the `package.json`, which is setting up `npm start` to invoke the `index.js` program with a command line argument of `sample.png`. It's a pretty generic image: 203 | 204 | ```js 205 | { 206 | "name": "png2bmp", 207 | "version": "0.0.1", 208 | "private": true, 209 | "gypfile": true, 210 | "scripts": { 211 | "start": "node index.js sample.png" 212 | }, 213 | "dependencies": { 214 | "nan": "*" 215 | } 216 | } 217 | ``` 218 | 219 | 220 | 221 | Here is the `binding.gyp` file - which is fairly standard, other than a few compiler flags needed to compile lodepng. It also includes the requisite references to NAN. 222 | 223 | ```js 224 | { 225 | "targets": [ 226 | { 227 | "target_name": "png2bmp", 228 | "sources": [ "png2bmp.cpp", "lodepng.cpp" ], 229 | "cflags": ["-Wall", "-Wextra", "-pedantic", "-ansi", "-O3"], 230 | "include_dirs" : ["` containing input data (png format) and a `vector` to put it's output (bmp format) data into. That function in turn calls `encodeBMP`, which is straight from the lodepng examples. Here is the full code listing of these two functions. The details are not important to understanding addon `Buffer` objects, but are included here for completeness. Our addon entry point(s) will call `do_convert`. 237 | 238 | ~~~~~~~~~~{#binding-hello .cpp} 239 | /* 240 | ALL LodePNG code in this file is adapted from lodepng's 241 | examples, found at the following URL: 242 | https://github.com/lvandeve/lodepng/blob/ 243 | master/examples/example_bmp2png.cpp' 244 | */ 245 | 246 | void encodeBMP(std::vector& bmp, 247 | const unsigned char* image, int w, int h) 248 | { 249 | //3 bytes per pixel used for both input and output. 250 | int inputChannels = 3; 251 | int outputChannels = 3; 252 | 253 | //bytes 0-13 254 | bmp.push_back('B'); bmp.push_back('M'); //0: bfType 255 | bmp.push_back(0); bmp.push_back(0); 256 | bmp.push_back(0); bmp.push_back(0); 257 | bmp.push_back(0); bmp.push_back(0); //6: bfReserved1 258 | bmp.push_back(0); bmp.push_back(0); //8: bfReserved2 259 | bmp.push_back(54 % 256); 260 | bmp.push_back(54 / 256); 261 | bmp.push_back(0); bmp.push_back(0); 262 | 263 | //bytes 14-53 264 | bmp.push_back(40); bmp.push_back(0); 265 | bmp.push_back(0); bmp.push_back(0); //14: biSize 266 | bmp.push_back(w % 256); 267 | bmp.push_back(w / 256); 268 | bmp.push_back(0); bmp.push_back(0); //18: biWidth 269 | bmp.push_back(h % 256); 270 | bmp.push_back(h / 256); 271 | bmp.push_back(0); bmp.push_back(0); //22: biHeight 272 | bmp.push_back(1); bmp.push_back(0); //26: biPlanes 273 | bmp.push_back(outputChannels * 8); 274 | bmp.push_back(0); //28: biBitCount 275 | bmp.push_back(0); bmp.push_back(0); 276 | bmp.push_back(0); bmp.push_back(0); //30: biCompression 277 | bmp.push_back(0); bmp.push_back(0); 278 | bmp.push_back(0); bmp.push_back(0); //34: biSizeImage 279 | bmp.push_back(0); bmp.push_back(0); 280 | bmp.push_back(0); bmp.push_back(0); //38: biXPelsPerMeter 281 | bmp.push_back(0); bmp.push_back(0); 282 | bmp.push_back(0); bmp.push_back(0); //42: biYPelsPerMeter 283 | bmp.push_back(0); bmp.push_back(0); 284 | bmp.push_back(0); bmp.push_back(0); //46: biClrUsed 285 | bmp.push_back(0); bmp.push_back(0); 286 | bmp.push_back(0); bmp.push_back(0); //50: biClrImportant 287 | 288 | int imagerowbytes = outputChannels * w; 289 | //must be multiple of 4 290 | imagerowbytes = imagerowbytes % 4 == 0 ? imagerowbytes : 291 | imagerowbytes + (4 - imagerowbytes % 4); 292 | 293 | for(int y = h - 1; y >= 0; y--) 294 | { 295 | int c = 0; 296 | for(int x = 0; x < imagerowbytes; x++) 297 | { 298 | if(x < w * outputChannels) 299 | { 300 | int inc = c; 301 | //Convert RGB(A) into BGR(A) 302 | if(c == 0) inc = 2; 303 | else if(c == 2) inc = 0; 304 | bmp.push_back(image[inputChannels 305 | * (w * y + x / outputChannels) + inc]); 306 | } 307 | else bmp.push_back(0); 308 | c++; 309 | if(c >= outputChannels) c = 0; 310 | } 311 | } 312 | 313 | // Fill in the size 314 | bmp[2] = bmp.size() % 256; 315 | bmp[3] = (bmp.size() / 256) % 256; 316 | bmp[4] = (bmp.size() / 65536) % 256; 317 | bmp[5] = bmp.size() / 16777216; 318 | } 319 | 320 | 321 | bool do_convert( 322 | std::vector & input_data, 323 | std::vector & bmp) 324 | { 325 | std::vector image; //the raw pixels 326 | unsigned width, height; 327 | unsigned error = lodepng::decode(image, width, 328 | height, input_data, LCT_RGB, 8); 329 | if(error) { 330 | std::cout << "error " << error << ": " 331 | << lodepng_error_text(error) 332 | << std::endl; 333 | return false; 334 | } 335 | encodeBMP(bmp, &image[0], width, height); 336 | return true; 337 | } 338 | ~~~~~~~~~~ 339 | 340 | Sorry... that listing was long, but it's important to see what's actually going on! Let's get to work bridging all this code to JavaScript. 341 | 342 | ## Synchronous Buffer Processing 343 | The png image data is actually read when we are in JavaScript, so it's passed in as a Node.js `Buffer`. We'll use NAN to access the buffer itself. Here's the complete code for the synchronous version. 344 | 345 | ```c++ 346 | void buffer_delete_callback(char* data, void* hint) { 347 | free(data); 348 | } 349 | 350 | NAN_METHOD(GetBMP) { 351 | unsigned char*buffer = (unsigned char*) node::Buffer::Data(info[0]->ToObject()); 352 | 353 | unsigned int size = info[1]->Uint32Value(); 354 | 355 | std::vector png_data(buffer, buffer + size); 356 | std::vector bmp = vector(); 357 | 358 | if ( do_convert(png_data, bmp)) { 359 | info.GetReturnValue().Set( 360 | NewBuffer( 361 | (char *)bmp.data(), 362 | bmp.size(), 363 | buffer_delete_callback, 364 | 0) 365 | .ToLocalChecked()); 366 | } 367 | } 368 | 369 | NAN_MODULE_INIT(Init) { 370 | Nan::Set(target, New("getBMP").ToLocalChecked(), 371 | GetFunction(New(GetBMP)).ToLocalChecked()); 372 | } 373 | 374 | NODE_MODULE(png2bmp, Init) 375 | ``` 376 | 377 | In `GetBMP` we use the familiar `Data` method to unwrap the buffer so we can work with it like a normal character array. Next, we build a `vector` around the input so we can pass it to our `do_convert` function listed above. Once the `bmp` vectors is filled in by `do_convert`, we wrap it up in a `Buffer` and return to JavaScript. 378 | 379 | Note the third parameter to the `NewBuffer` function. It is a callback - which ends up being called when the `Buffer` gets garbage collected by V8. Recall, `Buffer`s are JavaScript objects, whose data is stored outside V8 - but the object itself is under V8's control. From this perspective, it should make sense that a callback would be handy - when V8 destroys the buffer, we need some way of freeing up the data we have created - which is passed into the callback as it's first parameter. The signature of the callback is defined by NAN - `Nan::FreeCallback()`. The seconds parameter is a hint to aid in deallocation, we can use it however we want. 380 | 381 | So - *here is the problem* with this code: The data contained in the buffer we return is likely deleted before our JavaScript gets to use it. Why? The `bmp` vector is going to go out of scope as our `GetBMP` function returns. C++ vector semantics hold that when the vector goes out of scope, the vector's destructor will delete all data within the vector - in this case, our bmp data! This is a huge problem, since the `Buffer` we send back to JavaScript will have it's data deleted out from under it. You might get away with this (race conditions are fun right?), but it will eventually cause your program to crash. 382 | 383 | We can fix this issue by utilizing 4th parameter to `NewBuffer` - which is a *hint* passed in to the deletion callback. Since our problem is that the vector containing bitmap data goes out of scope, we can instead *dynamically* allocate the vector itself, and pass it into the free callback, where it can be properly deleted when the `Buffer` has been garbage collected. Below is the new `delete_callback`, along with the new call to `NewBuffer`. 384 | 385 | ```c++ 386 | void buffer_delete_callback(char* data, void* the_vector) { 387 | delete reinterpret_cast *> (the_vector); 388 | } 389 | 390 | NAN_METHOD(GetBMP) { 391 | 392 | unsigned char*buffer = (unsigned char*) node::Buffer::Data(info[0]->ToObject()); 393 | unsigned int size = info[1]->Uint32Value(); 394 | 395 | std::vector png_data(buffer, buffer + size); 396 | std::vector * bmp = new vector(); 397 | 398 | if ( do_convert(png_data, *bmp)) { 399 | info.GetReturnValue().Set( 400 | NewBuffer( 401 | (char *)bmp->data(), 402 | bmp->size(), 403 | buffer_delete_callback, 404 | bmp) 405 | .ToLocalChecked()); 406 | } 407 | } 408 | ``` 409 | 410 | Run this program by doing an `npm install` and then an `npm start` and you'll see a `sample.bmp` generated that looks eerily similar to `sample.png`, just a whole lot bigger (bmp compression is far less efficient than png). 411 | 412 | ## Asynchronous Buffer Processing 413 | Let's develop an asynchronous version of the png to bitmap converter. We'll perform the actual conversion in a C++ worker thread, using `Nan::AsyncWorker`. Through the use of `Buffer` objects we can avoid copying the png data - we will only need to hold a pointer to the underlying data such that our worker thread can access it. Likewise, the data produced by the worker thread (the `bmp` vector) can be used to create a new `Buffer` without copying data. 414 | 415 | ```c++ 416 | class PngToBmpWorker : public AsyncWorker { 417 | public: 418 | PngToBmpWorker(Callback * callback, 419 | v8::Local &pngBuffer, int size) 420 | : AsyncWorker(callback) { 421 | unsigned char*buffer = 422 | (unsigned char*) node::Buffer::Data(pngBuffer); 423 | 424 | std::vector tmp( 425 | buffer, 426 | buffer + (unsigned int) size); 427 | 428 | png_data = tmp; 429 | } 430 | void Execute() { 431 | bmp = new vector(); 432 | do_convert(png_data, *bmp); 433 | } 434 | void HandleOKCallback () { 435 | Local bmpData = 436 | NewBuffer((char *)bmp->data(), 437 | bmp->size(), buffer_delete_callback, 438 | bmp).ToLocalChecked(); 439 | Local argv[] = { bmpData }; 440 | callback->Call(1, argv); 441 | } 442 | 443 | private: 444 | vector png_data; 445 | std::vector * bmp; 446 | }; 447 | 448 | NAN_METHOD(GetBMPAsync) { 449 | int size = To(info[1]).FromJust(); 450 | v8::Local pngBuffer = 451 | info[0]->ToObject(); 452 | 453 | Callback *callback = 454 | new Callback(info[2].As()); 455 | 456 | AsyncQueueWorker( 457 | new PngToBmpWorker(callback, pngBuffer , size)); 458 | } 459 | 460 | ``` 461 | 462 | Our new `GetBMPAsync` addon function first unwraps the input buffer sent from JavaScript and then initializes and queues a new worker - `PngToBmpWorker` - using NAN's API. The worker object's `Execute` method is called by `libuv` inside a worker thread - where the conversion is done. When the `Execute` function returns, `libuv` calls the `HandleOKCallback` in the Node.js event loop thread - which creates the buffer and invokes the callback sent from JavaScript. 463 | 464 | Now we can utilize this addon function in JavaScript like this: 465 | 466 | ```js 467 | png2bmp.getBMPAsync(png_buffer, 468 | png_buffer.length, 469 | function(bmp_buffer) { 470 | fs.writeFileSync(bmp_file, bmp_buffer); 471 | }); 472 | ``` 473 | 474 | # Summary 475 | There were two core takeways in this post: 476 | 477 | 1. You can't ignore the costs of copying data between V8 storage cells and C++ variables - if you aren't careful, you can easily kill the performance boost you might have thought you were getting by dropping into C++ to perform your work! 478 | 2. Buffers offer a way to work with the same data in both JavaScript and C++ - thus avoiding the need to create copies. 479 | 480 | Through a simple demo application that rotate ascii text, along with more practical synchronous and asynchronous image conversion examples, I hope you now see how using buffers in your addons can be pretty painless. Hopefully this post helps you boost the performance of your own addons! 481 | 482 | If you are looking for some more tips on how to design Node.js C++ addons, please check out my [ebook on C++ and Node.js Integration](https://scottfrees.com/ebooks/nodecpp/). 483 | 484 | 1: Converting from png to bmp is *not* particularly time consuming, it's probably overkill for an addon - but it's good for demonstration purposes. If you are looking for a pure JavaScript implementation of image processing (including much more than png to bmp conversion), take a look at JIMP at [https://www.npmjs.com/package/jimp]()https://www.npmjs.com/package/jimp. -------------------------------------------------------------------------------- /buffers/typed/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "typed_example", 5 | "sources": [ "typed_example.cpp" ], 6 | "include_dirs" : [" 2 | using namespace Nan; 3 | using namespace v8; 4 | 5 | NAN_METHOD(increment) { 6 | Local array = Local::Cast(info[0]); 7 | double inc = Nan::To(info[1]).FromJust(); 8 | TypedArrayContents typed(array); 9 | double* data = *typed; 10 | for (u_int32_t i = 0; i < typed.length(); i++ ) { 11 | data[i] += inc; 12 | } 13 | } 14 | 15 | NAN_MODULE_INIT(Init) { 16 | Nan::Set(target, New("increment").ToLocalChecked(), 17 | GetFunction(New(increment)).ToLocalChecked()); 18 | } 19 | 20 | NODE_MODULE(typed_example, Init) -------------------------------------------------------------------------------- /conversions/index.js: -------------------------------------------------------------------------------- 1 | const loose = require('./loose/build/Release/loose_type_demo'); 2 | const strict = require('./strict/build/Release/strict_type_demo'); 3 | 4 | var assert = require('assert'); 5 | 6 | 7 | function reverse(s) { 8 | return s.split('').reverse().join(''); 9 | } 10 | 11 | describe('loose conversions', function() { 12 | 13 | /* pass_number accepts any numeric value and adds 42 to it. 14 | pass_integer accepts an integer value and adds 42 to it. 15 | loose intepretation of numbers in V8 mimic's exactly what 16 | we'd expect in JavaScript proper. 17 | */ 18 | describe('pass_number()', function () { 19 | it('should return input + 42 when given a valid number', function () { 20 | assert.equal(23+42, loose.pass_number(23)); 21 | assert.equal(0.5+42, loose.pass_number(0.5)); 22 | }); 23 | 24 | it('should return input + 42 when given numeric value as a string', function () { 25 | assert.equal(23+42, loose.pass_number("23")); 26 | assert.equal(0.5+42, loose.pass_number("0.5")); 27 | }); 28 | 29 | it('should return 42 when given null (null converts to 0)', function () { 30 | assert.equal(42, loose.pass_number(null)); 31 | }); 32 | 33 | it('should return NaN when given undefined (undefined converts to NaN)', function () { 34 | assert(isNaN(loose.pass_number())); 35 | assert(isNaN(loose.pass_number(undefined))); 36 | }); 37 | 38 | it('should return NaN when given a string that cannot be parsed to number', function () { 39 | assert(isNaN(loose.pass_number("this is not a number"))); 40 | }); 41 | 42 | it('should return 42 when given an empty array ([] converts to 0)', function () { 43 | assert(isNaN(loose.pass_number({}))); 44 | }); 45 | 46 | it('should return NaN when given a non-empty object/array', function () { 47 | assert(isNaN(loose.pass_number({}))); 48 | assert(isNaN(loose.pass_number({x: 5}))); 49 | assert(isNaN(loose.pass_number([1, 2]))); 50 | }); 51 | 52 | }); 53 | 54 | 55 | 56 | 57 | 58 | describe('pass_integer()', function () { 59 | it('should return input + 42 when given integer value', function () { 60 | assert.equal(5+42, loose.pass_integer(5)); 61 | }); 62 | it('should return Math.floor(input) + 42 when given floating point value', function () { 63 | assert.equal(Math.floor(5)+42, loose.pass_integer(5.7)); 64 | }); 65 | }); 66 | 67 | 68 | 69 | 70 | describe('pass_string()', function () { 71 | /* pass_string accepts any string value and returns the reverse of it. 72 | loose intepretation of strings in V8 mimic's exactly what 73 | we'd expect in JavaScript proper. 74 | */ 75 | 76 | it('should return reverse a proper string', function () { 77 | assert.equal(reverse("The truth is out there"), loose.pass_string("The truth is out there")); 78 | }); 79 | it('should return reverse a numeric since numbers are turned into strings', function () { 80 | assert.equal("24", loose.pass_string(42)); 81 | assert.equal("eurt", loose.pass_string(true)); 82 | }); 83 | it('should return "llun" when given null - null is turned into "null"', function () { 84 | assert.equal("llun", loose.pass_string(null)); 85 | }); 86 | it('should return "denifednu" when given undefined - null is turned into "undefined"', function () { 87 | assert.equal(reverse("undefined"), loose.pass_string(undefined)); 88 | }); 89 | it('should return reverse of object serialized to string when given object', function () { 90 | assert.equal(reverse("[object Object]"), loose.pass_string({x: 5})); 91 | }); 92 | it('should return reverse of array serialized to string when given array', function () { 93 | assert.equal(reverse("9,0"), loose.pass_string([9, 0])); 94 | }); 95 | }); 96 | 97 | 98 | describe('pass_boolean()', function () { 99 | /* pass_boolean accepts any boolean value and returns the COMPLIMENT. 100 | loose intepretation of booleans in V8 mimic's exactly what 101 | we'd expect in JavaScript proper. 102 | */ 103 | 104 | it('should return compliment of proper boolean', function () { 105 | assert(loose.pass_boolean(true) == false); 106 | assert(loose.pass_boolean(false) == true); 107 | }); 108 | 109 | it('should return false if given anything considered "truthy', function () { 110 | assert(loose.pass_boolean(42) == false); 111 | assert(loose.pass_boolean("is this true?") == false); 112 | assert(loose.pass_boolean({}) == false); 113 | assert(loose.pass_boolean({x: 5}) == false); 114 | assert(loose.pass_boolean([]) == false); 115 | assert(loose.pass_boolean([1, 2]) == false); 116 | }); 117 | 118 | it('should return true if given anything considered "falsey', function () { 119 | assert(loose.pass_boolean(0)); 120 | assert(loose.pass_boolean(undefined)); 121 | assert(loose.pass_boolean(null)); 122 | }); 123 | 124 | }); 125 | 126 | describe('pass_object()', function () { 127 | /* pass_object accepts any object and extracts numeric x/y properties. It 128 | then constructs a new object with the sum and product of x/y. It 129 | demonstrates how properties can be handled with objects in V8. 130 | */ 131 | 132 | it('should fully compute properties given object with required properties', function () { 133 | assert.equal(13, loose.pass_object({x:3, y:10}).sum); 134 | assert.equal(30, loose.pass_object({x:3, y:10}).product); 135 | }); 136 | it('should set sum and product to NaN given object without x or y', function () { 137 | assert(13, isNaN(loose.pass_object({y:10}).sum)); 138 | assert(30, isNaN(loose.pass_object({x:3}).product)); 139 | assert(isNaN(loose.pass_object({x:3, y:"hello"}).product)); 140 | }); 141 | it('should fully compute properties given object with required properties - even when not pure numerics', function () { 142 | assert.equal(4, loose.pass_object({x:3, y:true}).sum); 143 | assert.equal(30, loose.pass_object({x:3, y:"10"}).product); 144 | assert.equal(0, loose.pass_object({x:3, y:null}).product); 145 | }); 146 | }); 147 | 148 | 149 | 150 | describe('pass_array()', function () { 151 | /* pass_array accepts an array (input) and assumes it has 3 numeric elements at index 0-2. 152 | It also looks for an additional property called "not_index". 153 | It contstructs a new array consisting of [input[0]+1, input.not_index, input[2]+1]. 154 | No one said these functions should be sensible ;) 155 | */ 156 | 157 | it('should fully compute given expected array', function () { 158 | var a = [4, 7, 9]; 159 | a.not_index = "hello"; 160 | assert.deepEqual([5, "hello", 10], loose.pass_array(a)); 161 | }); 162 | it('should return array with undefined values given incomplete input', function() { 163 | assert.deepEqual([2, undefined, 4], loose.pass_array([1, 2, 3])); 164 | assert.deepEqual([2, undefined, undefined], loose.pass_array([1, 2])); 165 | assert.deepEqual([undefined, undefined, undefined], loose.pass_array([])); 166 | }); 167 | }); 168 | }); 169 | 170 | 171 | //--------------------------------------- 172 | // Strict module does exactly the same thing to input as loose example, but if there 173 | // are ANY deviations from true data type, undefined is returned. Check this module's code 174 | // to see how error handling and strict type conversions can be handled in V8 175 | 176 | describe('strict conversions', function() { 177 | 178 | 179 | describe('pass_number()', function () { 180 | it('should return input + 42 when given a valid number', function () { 181 | assert.equal(23+42, strict.pass_number(23)); 182 | assert.equal(0.5+42, strict.pass_number(0.5)); 183 | }); 184 | 185 | it('should return undefined any time anything by the exact expected input is given', function () { 186 | assert.equal(undefined, strict.pass_number("23")); 187 | assert.equal(undefined, strict.pass_number("0.5")); 188 | assert.equal(undefined, strict.pass_number()); 189 | assert.equal(undefined, strict.pass_number(null)); 190 | assert.equal(undefined, strict.pass_number(undefined)); 191 | assert.equal(undefined, strict.pass_number("this is not a number")); 192 | assert.equal(undefined, strict.pass_number({})); 193 | assert.equal(undefined, strict.pass_number({x: 5})); 194 | assert.equal(undefined, strict.pass_number([1, 2])); 195 | }); 196 | }); 197 | 198 | 199 | describe('pass_integer()', function () { 200 | it('should return input + 42 when given integer value', function () { 201 | assert.equal(5+42, strict.pass_integer(5)); 202 | }); 203 | it('should return undefined any time anything by the exact expected input is given', function () { 204 | assert.equal(undefined, strict.pass_integer(5.7)); 205 | // pass_integer deals with non numbers the same as pass_number... 206 | }); 207 | }); 208 | 209 | 210 | describe('pass_string()', function () { 211 | 212 | it('should return reverse a proper string', function () { 213 | assert.equal(reverse("The truth is out there"), strict.pass_string("The truth is out there")); 214 | }); 215 | 216 | it('should return undefined any time anything by the exact expected input is given', function () { 217 | assert.equal(undefined, strict.pass_string(42)); 218 | assert.equal(undefined, strict.pass_string(true)); 219 | assert.equal(undefined, strict.pass_string(null)); 220 | assert.equal(undefined, strict.pass_string(undefined)); 221 | assert.equal(undefined, strict.pass_string({x: 5})); 222 | assert.equal(undefined, strict.pass_string([9, 0])); 223 | }); 224 | }); 225 | 226 | 227 | describe('pass_boolean()', function () { 228 | 229 | 230 | it('should return compliment of proper boolean', function () { 231 | assert(strict.pass_boolean(true) == false); 232 | assert(strict.pass_boolean(false) == true); 233 | }); 234 | 235 | it('should return undefined any time anything by the exact expected input is given', function () { 236 | assert.equal(undefined, strict.pass_boolean(42)); 237 | assert.equal(undefined, strict.pass_boolean("is this true?")); 238 | assert.equal(undefined, strict.pass_boolean("true")); 239 | assert.equal(undefined, strict.pass_boolean({})); 240 | assert.equal(undefined, strict.pass_boolean({x: 5})); 241 | assert.equal(undefined, strict.pass_boolean([])); 242 | assert.equal(undefined, strict.pass_boolean([1, 2])); 243 | assert.equal(undefined, strict.pass_boolean()); 244 | assert.equal(undefined, strict.pass_boolean(undefined)); 245 | assert.equal(undefined, strict.pass_boolean(null)); 246 | }); 247 | 248 | 249 | 250 | }); 251 | 252 | describe('pass_object()', function () { 253 | 254 | 255 | it('should fully compute properties given object with required properties', function () { 256 | assert.equal(13, strict.pass_object({x:3, y:10}).sum); 257 | assert.equal(30, strict.pass_object({x:3, y:10}).product); 258 | }); 259 | it('should return undefined any time anything by the exact expected input is given', function () { 260 | assert.equal(undefined, strict.pass_object({y:10})); 261 | assert.equal(undefined, strict.pass_object({x:3})); 262 | assert.equal(undefined, strict.pass_object({x:3, y:"hello"})); 263 | assert.equal(undefined, strict.pass_object({x:3, y:true})); 264 | assert.equal(undefined, strict.pass_object({x:3, y:"10"})); 265 | assert.equal(undefined, strict.pass_object({x:3, y:null})); 266 | }); 267 | }); 268 | 269 | 270 | 271 | describe('pass_array()', function () { 272 | 273 | it('should fully compute given expected array', function () { 274 | var a = [4, 7, 9]; 275 | a.not_index = "hello"; 276 | assert.deepEqual([5, "hello", 10], strict.pass_array(a)); 277 | }); 278 | it('should return array with undefined values given incomplete input', function() { 279 | assert.equal(undefined, strict.pass_array([1, 2, 3])); 280 | assert.equal(undefined, strict.pass_array([1, 2])); 281 | assert.equal(undefined, strict.pass_array([])); 282 | }); 283 | }); 284 | 285 | }); 286 | 287 | 288 | 289 | 290 | -------------------------------------------------------------------------------- /conversions/loose/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "loose_type_demo", 5 | "sources": [ "loose_type_demo.cpp" ] 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /conversions/loose/loose_type_demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace v8; 7 | 8 | /* Demonstrates converstions of v8:Value's passed as arguments. 9 | See https://v8docs.nodesource.com/node-0.8/dc/d0a/classv8_1_1_value.html 10 | for more types and conversion methods 11 | */ 12 | Local make_return(Isolate * isolate, const Local input ) ; 13 | 14 | 15 | void PassNumber(const FunctionCallbackInfo& args) { 16 | Isolate * isolate = args.GetIsolate(); 17 | 18 | double value = args[0]->NumberValue(); 19 | 20 | Local retval = Number::New(isolate, value + 42); 21 | args.GetReturnValue().Set(retval); 22 | } 23 | 24 | void PassInteger(const FunctionCallbackInfo& args) { 25 | Isolate * isolate = args.GetIsolate(); 26 | 27 | int value = args[0]->Int32Value(); 28 | 29 | Local retval = Int32::New(isolate, value + 42); 30 | args.GetReturnValue().Set(retval); 31 | } 32 | 33 | void PassBoolean(const FunctionCallbackInfo& args) { 34 | Isolate * isolate = args.GetIsolate(); 35 | Local target = args[0]->ToBoolean(); 36 | bool value = target->BooleanValue(); 37 | 38 | Local retval = Boolean::New(isolate, !value); 39 | args.GetReturnValue().Set(retval); 40 | } 41 | 42 | void PassString(const FunctionCallbackInfo& args) { 43 | Isolate * isolate = args.GetIsolate(); 44 | 45 | v8::String::Utf8Value s(args[0]); 46 | std::string str(*s, s.length()); 47 | std::reverse(str.begin(), str.end()); 48 | 49 | Local retval = String::NewFromUtf8(isolate, str.c_str()); 50 | args.GetReturnValue().Set(retval); 51 | } 52 | 53 | void PassObject(const FunctionCallbackInfo& args) { 54 | Isolate * isolate = args.GetIsolate(); 55 | Local target = args[0]->ToObject(); 56 | 57 | Local obj = make_return(isolate, target); 58 | 59 | args.GetReturnValue().Set(obj); 60 | } 61 | 62 | void PassArray(const FunctionCallbackInfo& args) { 63 | Isolate * isolate = args.GetIsolate(); 64 | Local array = Local::Cast(args[0]); 65 | 66 | for (unsigned int i = 0; i < array->Length(); i++ ) { 67 | if (array->Has(i)) { 68 | double value = array->Get(i)->NumberValue(); 69 | array->Set(i, Number::New(isolate, value + 1)); 70 | } 71 | } 72 | 73 | Local prop = String::NewFromUtf8(isolate, "not_index"); 74 | Local a = Array::New(isolate); 75 | a->Set(0, array->Get(0)); 76 | a->Set(1, array->Get(prop)); 77 | a->Set(2, array->Get(2)); 78 | 79 | args.GetReturnValue().Set(a); 80 | } 81 | 82 | 83 | 84 | Local make_return(Isolate * isolate, const Local input ) { 85 | Local x_prop = String::NewFromUtf8(isolate, "x"); 86 | Local y_prop = String::NewFromUtf8(isolate, "y"); 87 | Local sum_prop = String::NewFromUtf8(isolate, "sum"); 88 | Local product_prop = String::NewFromUtf8(isolate, "product"); 89 | 90 | double x = input->Get(x_prop)->NumberValue(); 91 | double y = input->Get(y_prop)->NumberValue(); 92 | 93 | Local obj = Object::New(isolate); 94 | obj->Set(sum_prop, Number::New(isolate, x + y)); 95 | obj->Set(product_prop, Number::New(isolate, x * y)); 96 | return obj; 97 | } 98 | 99 | 100 | 101 | 102 | void init(Local exports) { 103 | NODE_SET_METHOD(exports, "pass_number", PassNumber); 104 | NODE_SET_METHOD(exports, "pass_integer", PassInteger); 105 | NODE_SET_METHOD(exports, "pass_string", PassString); 106 | NODE_SET_METHOD(exports, "pass_boolean", PassBoolean); 107 | NODE_SET_METHOD(exports, "pass_object", PassObject); 108 | NODE_SET_METHOD(exports, "pass_array", PassArray); 109 | } 110 | 111 | NODE_MODULE(loose_type_demo, init) 112 | 113 | -------------------------------------------------------------------------------- /conversions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "conversions", 3 | "version": "1.0.0", 4 | "description": "Cheat sheet for JavaScript to C++ type conversions in V8", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha index.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/freezer333/nodecpp-demo.git" 12 | }, 13 | "author": "Scott Frees (http://scottfrees.com/)", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/freezer333/nodecpp-demo/issues" 17 | }, 18 | "homepage": "https://github.com/freezer333/nodecpp-demo#readme" 19 | } 20 | -------------------------------------------------------------------------------- /conversions/readme.md: -------------------------------------------------------------------------------- 1 | # Type conversions from JavaScript to C++ in V8 2 | Learning how to pass information from JavaScript to C++ addons can be tricky. Confusion stems from the extreme differences between the JavaScript and C++ type system. While C++ is strongly typed ("42" is not an integer, but a string, and only a string!), JavaScript is very eager to convert datatypes for us. 3 | 4 | JavaScript primitives include Strings, Numbers, and Booleans, along with others like `null` and `undefined`. V8 uses an inheritance hierarchy where `Primitive` extends `Value`, and all the individual primitives subclass `Primitive`. In addition to the standard JavaScript primitives, V8 also supports integers (`Int32` and `Uint32`). You can see all the rest of the Value types [here](https://v8docs.nodesource.com/io.js-3.0/dc/d0a/classv8_1_1_value.html). 5 | 6 | In this post, I'll show you some basics for working with numerics, and I'll point you to a **cheat sheet** I've used to learn how to do the [rest of the conversions](https://github.com/freezer333/nodecpp-demo/tree/master/conversions). I also cover this topic in a lot more detail in my upcoming ebook on [Node.js C++ Integration](http://scottfrees.com/ebooks/nodecpp/). 7 | 8 | All references to JavaScript values are held in C++ through a Handle object - in most cases a `Local`. The handles point to V8 storage cells within JavaScript runtime. You can learn a lot more about storage cells in my previous post on [memory issues in V8](http://blog.scottfrees.com/how-not-to-access-node-js-from-c-worker-threads). 9 | 10 | As you go through the API for working with primitives, you'll notice there is no assignment of `Local` objects - which at first may seem odd! It makes a lot of sense, however, for three reasons: 11 | 12 | 1. JavaScript primitives are *immutable*. Variables "containing" primitives are just pointing to unchanging V8 storage cells. Assignment of a `var x = 5;` makes `x` point to a storage cell with 5 in it - reassigning `x = 6` does not change this storage cell. Instead, it simply makes `x` point to another storage cell that contains 6. If `x` and `y` are both assigned the value of `10`, they both point to the same storage cell. 13 | 2. Function calls are pass-by-value. Whenever JavaScript calls a C++ addon with a primitive parameter, the addon always receives a distinct *copy*. This means that changing its value in C++ has no visible effect to the caller. 14 | 3. Handles (`Local`) are references to *storage cells*. Thus, given #1 above, it doesn't make sense to allow handle values to change - since primitives don't change! 15 | 16 | Hopefully that makes some sense. However it's still likely you'll need to *modify* V8 variables. We'll just need to do this by *creating* new ones and assigning the new value to them. 17 | 18 | ## Number Example 19 | Now let's look at the Number primitive type and what happens when we construct a C++ addon to accept them from JavaScript. I've built an addon that exposes the following C++ function as an export called `pass_number`: 20 | 21 | ```c++ 22 | void PassNumber(const FunctionCallbackInfo& args) { 23 | Isolate * isolate = args.GetIsolate(); 24 | 25 | double value = args[0]->NumberValue(); 26 | 27 | value += 42; 28 | 29 | Local retval = Number::New(isolate, value); 30 | 31 | args.GetReturnValue().Set(retval); 32 | } 33 | ``` 34 | 35 | The complete addon code is [here](https://github.com/freezer333/nodecpp-demo/blob/master/conversions/loose/loose_type_demo.cpp). 36 | 37 | The addon does virtually nothing to ensure that the argument passed into the function is a Number, or if it even exists! Here's the associated mocha tests - we can see how V8 handles numbers, and more importantly, other input that can or cannot be converted to numbers in JavaScript. 38 | 39 | ```javascript 40 | 41 | describe('pass_number()', function () { 42 | it('return input + 42 when given a valid number', function () { 43 | assert.equal(23+42, loose.pass_number(23)); 44 | assert.equal(0.5+42, loose.pass_number(0.5)); 45 | }); 46 | 47 | it('return input + 42 when given numeric as a string', function () { 48 | assert.equal(23+42, loose.pass_number('23')); 49 | assert.equal(0.5+42, loose.pass_number('0.5')); 50 | }); 51 | 52 | it('return 42 when given null (null converts to 0)', function () { 53 | assert.equal(42, loose.pass_number(null)); 54 | }); 55 | 56 | it('return NaN when given undefined', function () { 57 | assert(isNaN(loose.pass_number())); 58 | assert(isNaN(loose.pass_number(undefined))); 59 | }); 60 | 61 | it('return NaN when given a non-number string', function () { 62 | assert(isNaN(loose.pass_number('this is not a number'))); 63 | }); 64 | 65 | ``` 66 | 67 | 68 | ## Complete type conversion cheat sheet 69 | I've created a [repository](https://github.com/freezer333/nodecpp-demo/tree/master/conversions) which has a type conversion cheat sheet that I think is pretty useful. To get it: 70 | 71 | ``` 72 | > git clone https://github.com/freezer333/nodecpp-demo.git 73 | ``` 74 | 75 | To build both addons, go into the `loose` and `strict` directories and issue a `node-gyp configure build` command in each. You'll need to have installed `node-gyp` globally first. If you're completely new to this, [check this out](http://blog.scottfrees.com/c-processing-from-node-js). 76 | 77 | ``` 78 | > cd nodecpp-demo/conversion/loose 79 | > node-gyp configure build 80 | ... 81 | > cd ../strict 82 | > node-gyp configure build 83 | ``` 84 | 85 | The two addons (loose and strict) expose a series of functions that accept different types - Numbers, Integers, Strings, Booleans, Objects, and Arrays - and perform (somewhat silly) operations on them before returning a value. I've included a JavaScript test program that shows you the expected outputs of each function - but the real learning value is in the addons' C++ code ([strict](https://github.com/freezer333/nodecpp-demo/blob/master/conversions/loose/loose_type_demo.cpp)/[loose](https://github.com/freezer333/nodecpp-demo/blob/master/conversions/strict/strict_type_demo.cpp)). 86 | 87 | To run the tests (you'll need `mocha` installed), go into the `conversions` directory (with `index.js`): 88 | 89 | ``` 90 | > npm test 91 | ``` 92 | 93 | The 'loose' addon has very little type checking. It's basically mimicking how a pure JavaScript function would work. For example, the `pass_string` function accepts anything that could be converted to a string in JavaScript and returns the reverse of it: 94 | 95 | ```javascript 96 | 97 | describe('pass_string()', function () { 98 | var str = "The truth is out there"; 99 | 100 | it('reverse a proper string', function () { 101 | assert.equal(reverse(str), loose.pass_string(str)); 102 | }); 103 | it('reverse a numeric/boolean since numbers are turned into strings', function () { 104 | assert.equal('24', loose.pass_string(42)); 105 | assert.equal('eurt', loose.pass_string(true)); 106 | }); 107 | it('return 'llun' when given null - null is turned into 'null'', function () { 108 | assert.equal('llun', loose.pass_string(null)); 109 | }); 110 | it('return 'denifednu' when given undefined', function () { 111 | assert.equal(reverse('undefined'), loose.pass_string(undefined)); 112 | }); 113 | it('return reverse of object serialized to string', function () { 114 | assert.equal(reverse('[object Object]'), loose.pass_string({x: 5})); 115 | }); 116 | it('return reverse of array serialized to string', function () { 117 | assert.equal(reverse('9,0'), loose.pass_string([9, 0])); 118 | }); 119 | }); 120 | ``` 121 | 122 | Here's the C++ code for the loose conversions of string input: 123 | 124 | ```c++ 125 | void PassString(const FunctionCallbackInfo& args) { 126 | Isolate * isolate = args.GetIsolate(); 127 | 128 | v8::String::Utf8Value s(args[0]); 129 | std::string str(*s, s.length()); 130 | std::reverse(str.begin(), str.end()); 131 | 132 | Local retval = String::NewFromUtf8(isolate, str.c_str()); 133 | args.GetReturnValue().Set(retval); 134 | } 135 | ``` 136 | 137 | The 'strict' addon performs full type and error checking, behaving more like a C++ function than a JavaScript function. For all of the strict addon methods, `undefined` is always returned if the input to the function isn't *exactly* what was expected. For example, the pass_string function behaves quite differently than the loose interpretation: 138 | 139 | ```javascript 140 | describe('pass_string()', function () { 141 | 142 | it('return reverse a proper string', function () { 143 | var str = 'The truth is out there'; 144 | it('reverse a proper string', function () { 145 | assert.equal(reverse(str), strict.pass_string(str)); 146 | }); 147 | }); 148 | 149 | it('return undefined for non-strings', function () { 150 | assert.equal(undefined, strict.pass_string(42)); 151 | assert.equal(undefined, strict.pass_string(true)); 152 | assert.equal(undefined, strict.pass_string(null)); 153 | assert.equal(undefined, strict.pass_string(undefined)); 154 | assert.equal(undefined, strict.pass_string({x: 5})); 155 | assert.equal(undefined, strict.pass_string([9, 0])); 156 | }); 157 | }); 158 | ``` 159 | 160 | ```c++ 161 | void PassString(const FunctionCallbackInfo& args) { 162 | Isolate * isolate = args.GetIsolate(); 163 | 164 | if ( args.Length() < 1 ) { 165 | return; 166 | } 167 | else if ( args[0]->IsNull() ) { 168 | return; 169 | } 170 | else if ( args[0]->IsUndefined() ) { 171 | return; 172 | } 173 | else if (!args[0]->IsString()) { 174 | // This clause would catch IsNull and IsUndefined too... 175 | return ; 176 | } 177 | 178 | v8::String::Utf8Value s(args[0]); 179 | std::string str(*s, s.length()); 180 | std::reverse(str.begin(), str.end()); 181 | 182 | Local retval = String::NewFromUtf8(isolate, str.c_str()); 183 | args.GetReturnValue().Set(retval); 184 | } 185 | ``` 186 | 187 | Go [ahead and download](https://github.com/freezer333/nodecpp-demo) the complete source and take a look. The code is in the `/conversions` directory. You'll see examples using Integers, Booleans, Objects, and Arrays. 188 | 189 | ## Looking for more info? 190 | You'll encounter similar code when using NAN, so reviewing the NAN project's writeups should shed some light on the topic. However, this post is actually a small excerpt from my recent book, which covers these and related issues in much gorier detail. If you are interested, click [here](http://scottfrees.com/ebooks/nodecpp/) for the table of contents and info on how to get your copy. 191 | -------------------------------------------------------------------------------- /conversions/strict/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "strict_type_demo", 5 | "sources": [ "strict_type_demo.cpp" ] 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /conversions/strict/strict_type_demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace v8; 7 | 8 | /* Demonstrates converstions of v8:Value's passed as arguments. 9 | See https://v8docs.nodesource.com/node-0.8/dc/d0a/classv8_1_1_value.html 10 | for more types and conversion methods 11 | */ 12 | Local make_return(Isolate * isolate, const Local input ) ; 13 | 14 | 15 | /* NOTE: Anytime you fail to set the return value - args.getReturnValue().Set(...) - 16 | your addon will return undefined 17 | */ 18 | 19 | void PassNumber(const FunctionCallbackInfo& args) { 20 | Isolate * isolate = args.GetIsolate(); 21 | 22 | if ( args.Length() < 1 ) { 23 | return; 24 | } 25 | 26 | if ( !args[0]->IsNumber()) { 27 | return; 28 | } 29 | 30 | double value = args[0]->NumberValue(); 31 | 32 | Local retval = Number::New(isolate, value + 42); 33 | args.GetReturnValue().Set(retval); 34 | } 35 | 36 | void PassInteger(const FunctionCallbackInfo& args) { 37 | Isolate * isolate = args.GetIsolate(); 38 | 39 | if ( args.Length() < 1 ) { 40 | return; 41 | } 42 | 43 | if ( !args[0]->IsInt32()) { 44 | return; 45 | } 46 | 47 | int value = args[0]->Int32Value(); 48 | 49 | Local retval = Int32::New(isolate, value + 42); 50 | args.GetReturnValue().Set(retval); 51 | } 52 | 53 | void PassBoolean(const FunctionCallbackInfo& args) { 54 | Isolate * isolate = args.GetIsolate(); 55 | 56 | if ( args.Length() < 1 || !args[0]->IsBoolean()) { 57 | return; 58 | } 59 | 60 | bool value = args[0]->BooleanValue(); 61 | 62 | Local retval = Boolean::New(isolate, !value); 63 | args.GetReturnValue().Set(retval); 64 | } 65 | 66 | void PassString(const FunctionCallbackInfo& args) { 67 | Isolate * isolate = args.GetIsolate(); 68 | 69 | if ( args.Length() < 1 ) { 70 | return; 71 | } 72 | else if ( args[0]->IsNull() ) { 73 | return; 74 | } 75 | else if ( args[0]->IsUndefined() ) { 76 | return; 77 | } 78 | else if (!args[0]->IsString()) { 79 | // This clause would catch IsNull and IsUndefined too... 80 | return ; 81 | } 82 | 83 | v8::String::Utf8Value s(args[0]); 84 | std::string str(*s, s.length()); 85 | std::reverse(str.begin(), str.end()); 86 | 87 | Local retval = String::NewFromUtf8(isolate, str.c_str()); 88 | args.GetReturnValue().Set(retval); 89 | } 90 | 91 | void PassObject(const FunctionCallbackInfo& args) { 92 | Isolate * isolate = args.GetIsolate(); 93 | 94 | if ( args.Length() < 1 || ! args[0]->IsObject()) { 95 | // just return undefined 96 | return; 97 | } 98 | 99 | Local target = args[0]->ToObject(); 100 | 101 | Local obj = make_return(isolate, target); 102 | 103 | args.GetReturnValue().Set(obj); 104 | } 105 | 106 | void PassArray(const FunctionCallbackInfo& args) { 107 | Isolate * isolate = args.GetIsolate(); 108 | Local array = Local::Cast(args[0]); 109 | 110 | if ( args.Length() < 1 || ! args[0]->IsArray()) { 111 | return; 112 | } 113 | 114 | if (array->Length() < 3) { 115 | return; 116 | } 117 | 118 | Local prop = String::NewFromUtf8(isolate, "not_index"); 119 | if (array->Get(prop)->IsUndefined() ){ 120 | return; 121 | } 122 | 123 | for (unsigned int i = 0; i < 3; i++ ) { 124 | if (array->Has(i)) { 125 | Local v = array->Get(i); 126 | if ( !v->IsNumber()) return; 127 | 128 | double value = v->NumberValue(); 129 | array->Set(i, Number::New(isolate, value + 1)); 130 | } 131 | else { 132 | return; 133 | } 134 | } 135 | 136 | Local a = Array::New(isolate); 137 | a->Set(0, array->Get(0)); 138 | a->Set(1, array->Get(prop)); 139 | a->Set(2, array->Get(2)); 140 | 141 | args.GetReturnValue().Set(a); 142 | } 143 | 144 | 145 | 146 | Local make_return(Isolate * isolate, const Local input ) { 147 | Local x_prop = String::NewFromUtf8(isolate, "x"); 148 | Local y_prop = String::NewFromUtf8(isolate, "y"); 149 | Local sum_prop = String::NewFromUtf8(isolate, "sum"); 150 | Local product_prop = String::NewFromUtf8(isolate, "product"); 151 | 152 | Local x_value = input->Get(x_prop); 153 | Local y_value = input->Get(y_prop); 154 | 155 | if ( !x_value->IsNumber() || !y_value->IsNumber()) { 156 | return v8::Undefined(isolate); 157 | } 158 | 159 | 160 | double x = input->Get(x_prop)->NumberValue(); 161 | double y = input->Get(y_prop)->NumberValue(); 162 | 163 | Local obj = Object::New(isolate); 164 | obj->Set(sum_prop, Number::New(isolate, x + y)); 165 | obj->Set(product_prop, Number::New(isolate, x * y)); 166 | return obj; 167 | } 168 | 169 | 170 | 171 | 172 | void init(Local exports) { 173 | NODE_SET_METHOD(exports, "pass_number", PassNumber); 174 | NODE_SET_METHOD(exports, "pass_integer", PassInteger); 175 | NODE_SET_METHOD(exports, "pass_string", PassString); 176 | NODE_SET_METHOD(exports, "pass_boolean", PassBoolean); 177 | NODE_SET_METHOD(exports, "pass_object", PassObject); 178 | NODE_SET_METHOD(exports, "pass_array", PassArray); 179 | } 180 | 181 | NODE_MODULE(strict_type_demo, init) 182 | -------------------------------------------------------------------------------- /conversions_nan/basic_nan.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using namespace std; 5 | using namespace Nan; 6 | using namespace v8; 7 | 8 | NAN_METHOD(PassNumber) { 9 | Nan::Maybe value = Nan::To(info[0]); 10 | Local retval = Nan::New(value.FromJust() + 42); 11 | info.GetReturnValue().Set(retval); 12 | } 13 | 14 | NAN_METHOD(PassInteger) { 15 | if ( info.Length() < 1 ) { 16 | return; 17 | } 18 | if ( !info[0]->IsInt32()) { 19 | return; 20 | } 21 | int value = info[0]->IntegerValue(); 22 | Local retval = Nan::New(value + 42); 23 | info.GetReturnValue().Set(retval); 24 | } 25 | 26 | NAN_METHOD(PassBoolean) { 27 | if ( info.Length() < 1 ) { 28 | return; 29 | } 30 | if ( !info[0]->IsBoolean()) { 31 | return; 32 | } 33 | bool value = info[0]->BooleanValue(); 34 | Local retval = Nan::New(!value); 35 | info.GetReturnValue().Set(retval); 36 | } 37 | 38 | 39 | NAN_METHOD(PassString) { 40 | v8::String::Utf8Value val(info[0]->ToString()); 41 | 42 | std::string str(*val, val.length()); 43 | std::reverse(str.begin(), str.end()); 44 | 45 | info.GetReturnValue().Set(Nan::New(str.c_str()).ToLocalChecked()); 46 | } 47 | 48 | Local make_return(const Local input ) { 49 | Local x_prop = Nan::New("x").ToLocalChecked(); 50 | Local y_prop = Nan::New("y").ToLocalChecked(); 51 | Local sum_prop = Nan::New("sum").ToLocalChecked(); 52 | Local product_prop = Nan::New("product").ToLocalChecked(); 53 | 54 | Local obj = Nan::New(); 55 | double x = Nan::Get(input, x_prop).ToLocalChecked()->NumberValue(); 56 | double y = Nan::Get(input, y_prop).ToLocalChecked()->NumberValue(); 57 | 58 | Nan::Set(obj, sum_prop, Nan::New(x+y)); 59 | Nan::Set(obj, product_prop, Nan::New(x*y)); 60 | return obj; 61 | } 62 | 63 | NAN_METHOD(PassObject) { 64 | if ( info.Length() > 0 ) { 65 | Local target = info[0]->ToObject(); 66 | Local obj = make_return(target); 67 | info.GetReturnValue().Set(obj); 68 | } 69 | } 70 | 71 | NAN_METHOD(IncrementArray) { 72 | Local array = Local::Cast(info[0]); 73 | 74 | for (unsigned int i = 0; i < array->Length(); i++ ) { 75 | if (Nan::Has(array, i).FromJust()) { 76 | double value = Nan::Get(array, i).ToLocalChecked()->NumberValue(); 77 | Nan::Set(array, i, Nan::New(value + 1)); 78 | } 79 | } 80 | 81 | Local prop = Nan::New("not_index").ToLocalChecked(); 82 | Local a = New(3); 83 | Nan::Set(a, 0, Nan::Get(array, 0).ToLocalChecked()); 84 | Nan::Set(a, 1, Nan::Get(array, prop).ToLocalChecked()); 85 | Nan::Set(a, 2, Nan::Get(array, 2).ToLocalChecked()); 86 | 87 | info.GetReturnValue().Set(a); 88 | } 89 | 90 | 91 | NAN_MODULE_INIT(Init) { 92 | Nan::Set(target, New("pass_number").ToLocalChecked(), 93 | GetFunction(New(PassNumber)).ToLocalChecked()); 94 | Nan::Set(target, New("pass_integer").ToLocalChecked(), 95 | GetFunction(New(PassInteger)).ToLocalChecked()); 96 | Nan::Set(target, New("pass_boolean").ToLocalChecked(), 97 | GetFunction(New(PassBoolean)).ToLocalChecked()); 98 | Nan::Set(target, New("pass_string").ToLocalChecked(), 99 | GetFunction(New(PassString)).ToLocalChecked()); 100 | 101 | Nan::Set(target, New("pass_object").ToLocalChecked(), 102 | GetFunction(New(PassObject)).ToLocalChecked()); 103 | 104 | Nan::Set(target, New("pass_array").ToLocalChecked(), 105 | GetFunction(New(IncrementArray)).ToLocalChecked()); 106 | } 107 | 108 | NODE_MODULE(basic_nan, Init) -------------------------------------------------------------------------------- /conversions_nan/basic_nan.js: -------------------------------------------------------------------------------- 1 | const addon = require('./build/Release/basic_nan'); 2 | 3 | 4 | console.log( addon.pass_number(5) ); 5 | console.log( addon.pass_number() ); 6 | console.log( addon.pass_number(function() { return "xyz"})); 7 | 8 | 9 | console.log( addon.pass_integer(4)); 10 | console.log( addon.pass_integer()); 11 | 12 | console.log( addon.pass_boolean(false)); 13 | 14 | console.log( addon.pass_string("helle")); 15 | 16 | 17 | var retval = addon.pass_object({x : 3, y: 10}); 18 | console.log(retval); 19 | 20 | retval = addon.pass_object({}); 21 | console.log(retval); 22 | retval = addon.pass_object(); 23 | console.log(retval); -------------------------------------------------------------------------------- /conversions_nan/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "basic_nan", 5 | "sources": [ "basic_nan.cpp" ], 6 | "include_dirs" : [ 7 | " (http://scottfrees.com/)", 10 | "license": "ISC", 11 | "dependencies": { 12 | "nan": "^2.3.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lambda-cpp/addon/average.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace v8; 4 | 5 | void Average(const FunctionCallbackInfo& args) { 6 | Isolate * isolate = args.GetIsolate(); 7 | double sum = 0; 8 | int count = 0; 9 | 10 | for (int i = 0; i < args.Length(); i++){ 11 | if ( args[i]->IsNumber()) { 12 | sum += args[i]->NumberValue(); 13 | count++; 14 | } 15 | } 16 | 17 | Local retval = Number::New(isolate, sum / count); 18 | args.GetReturnValue().Set(retval); 19 | } 20 | 21 | 22 | void init(Local exports) { 23 | NODE_SET_METHOD(exports, "average", Average); 24 | } 25 | 26 | NODE_MODULE(average_addon, init) -------------------------------------------------------------------------------- /lambda-cpp/addon/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "average", 5 | "sources": [ "average.cpp" ] 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /lambda-cpp/addon/output.txt: -------------------------------------------------------------------------------- 1 | 7 -------------------------------------------------------------------------------- /lambda-cpp/addon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "average", 3 | "version": "1.0.0", 4 | "main": "./build/Release/average", 5 | "gypfile": true, 6 | "author": "Scott Frees (http://scottfrees.com/)", 7 | "license": "ISC" 8 | } 9 | -------------------------------------------------------------------------------- /lambda-cpp/average.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freezer333/nodecpp-demo/9130c01631ba20048c3fffc3632eadcb94971724/lambda-cpp/average.zip -------------------------------------------------------------------------------- /lambda-cpp/lambda/index.js: -------------------------------------------------------------------------------- 1 | exports.averageHandler = function(event, context, callback) { 2 | const addon = require('average'); 3 | console.log(event); 4 | var result = addon.average(event.op1, event.op2, event.op3) 5 | console.log(result); 6 | callback(null, result); 7 | } -------------------------------------------------------------------------------- /lambda-cpp/lambda/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-demo", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Scott Frees (http://scottfrees.com/)", 6 | "license": "ISC", 7 | "dependencies": { 8 | "average": "file:../addon" 9 | } 10 | } -------------------------------------------------------------------------------- /lambda-cpp/lambda/sample.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | op1: 4, 3 | op2: 15, 4 | op3: 2 5 | }; -------------------------------------------------------------------------------- /objectwrap/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "polynomial", 5 | "sources": [ "polynomial.cpp" ], 6 | "cflags": ["-Wall", "-std=c++11"], 7 | 'xcode_settings': { 8 | 'OTHER_CFLAGS': [ 9 | '-std=c++11' 10 | ], 11 | }, 12 | "conditions": [ 13 | [ 'OS=="mac"', { 14 | "xcode_settings": { 15 | 'OTHER_CPLUSPLUSFLAGS' : ['-std=c++11','-stdlib=libc++'], 16 | 'OTHER_LDFLAGS': ['-stdlib=libc++'], 17 | 'MACOSX_DEPLOYMENT_TARGET': '10.7' } 18 | } 19 | ] 20 | ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /objectwrap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polynomial", 3 | "version": "0.0.1", 4 | "private": true, 5 | "gypfile": true, 6 | "scripts": { 7 | "start": "node polynomial.js" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /objectwrap/polynomial.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | using namespace v8; 7 | 8 | class WrappedPoly : public node::ObjectWrap 9 | { 10 | public: 11 | static void Init(v8::Local exports) 12 | { 13 | Isolate *isolate = exports->GetIsolate(); 14 | 15 | // Prepare constructor template 16 | Local tpl = FunctionTemplate::New(isolate, New); 17 | tpl->SetClassName(String::NewFromUtf8(isolate, "Polynomial")); 18 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 19 | 20 | // Prototype 21 | NODE_SET_PROTOTYPE_METHOD(tpl, "at", At); 22 | NODE_SET_PROTOTYPE_METHOD(tpl, "roots", Roots); 23 | 24 | tpl->InstanceTemplate()->SetAccessor(String::NewFromUtf8(isolate, "a"), GetCoeff, SetCoeff); 25 | tpl->InstanceTemplate()->SetAccessor(String::NewFromUtf8(isolate, "b"), GetCoeff, SetCoeff); 26 | tpl->InstanceTemplate()->SetAccessor(String::NewFromUtf8(isolate, "c"), GetCoeff, SetCoeff); 27 | 28 | constructor.Reset(isolate, tpl->GetFunction()); 29 | exports->Set(String::NewFromUtf8(isolate, "Polynomial"), 30 | tpl->GetFunction()); 31 | } 32 | 33 | private: 34 | explicit WrappedPoly(double a = 0, double b = 0, double c = 0) 35 | : a_(a), b_(b), c_(c) {} 36 | ~WrappedPoly() {} 37 | 38 | static void New(const v8::FunctionCallbackInfo &args) 39 | { 40 | Isolate *isolate = args.GetIsolate(); 41 | 42 | if (args.IsConstructCall()) 43 | { 44 | // Invoked as constructor: `new Polynomial(...)` 45 | double a = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); 46 | double b = args[1]->IsUndefined() ? 0 : args[1]->NumberValue(); 47 | double c = args[2]->IsUndefined() ? 0 : args[2]->NumberValue(); 48 | WrappedPoly *obj = new WrappedPoly(a, b, c); 49 | obj->Wrap(args.This()); 50 | args.GetReturnValue().Set(args.This()); 51 | } 52 | else 53 | { 54 | // Invoked as plain function `Polynomial(...)`, turn into construct call. 55 | const int argc = 3; 56 | Local argv[argc] = {args[0], args[1], args[2]}; 57 | Local cons = Local::New(isolate, constructor); 58 | args.GetReturnValue().Set(cons->NewInstance(isolate->GetCurrentContext(), argc, argv).ToLocalChecked()); 59 | } 60 | } 61 | static void At(const v8::FunctionCallbackInfo &args); 62 | static void Roots(const v8::FunctionCallbackInfo &args); 63 | 64 | static void GetCoeff(Local property, const PropertyCallbackInfo &info); 65 | static void SetCoeff(Local property, Local value, const PropertyCallbackInfo &info); 66 | 67 | static v8::Persistent constructor; 68 | double a_; 69 | double b_; 70 | double c_; 71 | }; 72 | 73 | Persistent WrappedPoly::constructor; 74 | 75 | void WrappedPoly::At(const v8::FunctionCallbackInfo &args) 76 | { 77 | Isolate *isolate = args.GetIsolate(); 78 | double x = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); 79 | WrappedPoly *poly = ObjectWrap::Unwrap(args.Holder()); 80 | 81 | double results = x * x * poly->a_ + x * poly->b_ + poly->c_; 82 | 83 | args.GetReturnValue().Set(Number::New(isolate, results)); 84 | } 85 | 86 | void WrappedPoly::Roots(const v8::FunctionCallbackInfo &args) 87 | { 88 | Isolate *isolate = args.GetIsolate(); 89 | WrappedPoly *poly = ObjectWrap::Unwrap(args.Holder()); 90 | 91 | Local roots = Array::New(isolate); 92 | double desc = poly->b_ * poly->b_ - (4 * poly->a_ * poly->c_); 93 | if (desc >= 0) 94 | { 95 | double r = (-poly->b_ + sqrt(desc)) / (2 * poly->a_); 96 | roots->Set(0, Number::New(isolate, r)); 97 | if (desc > 0) 98 | { 99 | r = (-poly->b_ - sqrt(desc)) / (2 * poly->a_); 100 | roots->Set(1, Number::New(isolate, r)); 101 | } 102 | } 103 | args.GetReturnValue().Set(roots); 104 | } 105 | 106 | void WrappedPoly::GetCoeff(Local property, const PropertyCallbackInfo &info) 107 | { 108 | Isolate *isolate = info.GetIsolate(); 109 | WrappedPoly *obj = ObjectWrap::Unwrap(info.This()); 110 | 111 | v8::String::Utf8Value s(property); 112 | std::string str(*s, s.length()); 113 | 114 | if (str == "a") 115 | { 116 | info.GetReturnValue().Set(Number::New(isolate, obj->a_)); 117 | } 118 | else if (str == "b") 119 | { 120 | info.GetReturnValue().Set(Number::New(isolate, obj->b_)); 121 | } 122 | else if (str == "c") 123 | { 124 | info.GetReturnValue().Set(Number::New(isolate, obj->c_)); 125 | } 126 | } 127 | 128 | void WrappedPoly::SetCoeff(Local property, Local value, const PropertyCallbackInfo &info) 129 | { 130 | WrappedPoly *obj = ObjectWrap::Unwrap(info.This()); 131 | 132 | v8::String::Utf8Value s(property); 133 | std::string str(*s, s.length()); 134 | 135 | if (str == "a") 136 | { 137 | obj->a_ = value->NumberValue(); 138 | } 139 | else if (str == "b") 140 | { 141 | obj->b_ = value->NumberValue(); 142 | } 143 | else if (str == "c") 144 | { 145 | obj->c_ = value->NumberValue(); 146 | } 147 | } 148 | 149 | void InitPoly(Local exports) 150 | { 151 | WrappedPoly::Init(exports); 152 | } 153 | 154 | NODE_MODULE(polynomial, InitPoly) -------------------------------------------------------------------------------- /objectwrap/polynomial.js: -------------------------------------------------------------------------------- 1 | const addon = require('./build/Release/polynomial'); 2 | 3 | var poly = new addon.Polynomial(1, 3, 2); 4 | 5 | console.log(poly.at(4)); 6 | console.log(poly.roots()); 7 | poly.c = 0; 8 | console.log(poly.at(4)); 9 | 10 | console.log(poly); -------------------------------------------------------------------------------- /objectwrap_nan/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "polynomial", 5 | "sources": [ "polynomial.cpp" ], 6 | "cflags": ["-Wall", "-std=c++11"], 7 | "include_dirs" : [" (/usr/local/lib/node_modules/npm/lib/utils/lifecycle.js:239:16) 76 | 69 verbose stack at emitTwo (events.js:100:13) 77 | 69 verbose stack at EventEmitter.emit (events.js:185:7) 78 | 69 verbose stack at ChildProcess. (/usr/local/lib/node_modules/npm/lib/utils/spawn.js:24:14) 79 | 69 verbose stack at emitTwo (events.js:100:13) 80 | 69 verbose stack at ChildProcess.emit (events.js:185:7) 81 | 69 verbose stack at maybeClose (internal/child_process.js:850:16) 82 | 69 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:215:5) 83 | 70 verbose pkgid primes@1.0.0 84 | 71 verbose cwd /Users/sfrees/projects/nodecpp-ebook/code/chapter07/polynomial 85 | 72 error Darwin 14.5.0 86 | 73 error argv "/usr/local/bin/node" "/usr/local/bin/npm" "install" 87 | 74 error node v5.10.1 88 | 75 error npm v3.8.3 89 | 76 error code ELIFECYCLE 90 | 77 error primes@1.0.0 install: `node-gyp rebuild` 91 | 77 error Exit status 1 92 | 78 error Failed at the primes@1.0.0 install script 'node-gyp rebuild'. 93 | 78 error Make sure you have the latest version of node.js and npm installed. 94 | 78 error If you do, this is most likely a problem with the primes package, 95 | 78 error not with npm itself. 96 | 78 error Tell the author that this fails on your system: 97 | 78 error node-gyp rebuild 98 | 78 error You can get information on how to open an issue for this project with: 99 | 78 error npm bugs primes 100 | 78 error Or if that isn't available, you can get their info via: 101 | 78 error npm owner ls primes 102 | 78 error There is likely additional logging output above. 103 | 79 verbose exit [ 1, true ] 104 | -------------------------------------------------------------------------------- /objectwrap_nan/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "primes", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Scott Frees (http://scottfrees.com/)", 10 | "license": "ISC", 11 | "dependencies": { 12 | "nan": "^2.3.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /objectwrap_nan/polynomial.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using namespace Nan; 5 | 6 | static Persistent constructor; 7 | 8 | class WrappedPoly : public Nan::ObjectWrap { 9 | public: 10 | static NAN_MODULE_INIT(Init) { 11 | v8::Local tpl = Nan::New(WrappedPoly::New); 12 | constructor.Reset(tpl); 13 | tpl->SetClassName(Nan::New("Polynomial").ToLocalChecked()); 14 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 15 | SetPrototypeMethod(tpl, "at", WrappedPoly::At); 16 | SetPrototypeMethod(tpl, "roots", WrappedPoly::Roots); 17 | v8::Local itpl = tpl->InstanceTemplate(); 18 | SetAccessor(itpl, Nan::New("a").ToLocalChecked(), WrappedPoly::GetCoeff, WrappedPoly::SetCoeff); 19 | SetAccessor(itpl, Nan::New("b").ToLocalChecked(), WrappedPoly::GetCoeff, WrappedPoly::SetCoeff); 20 | SetAccessor(itpl, Nan::New("c").ToLocalChecked(), WrappedPoly::GetCoeff, WrappedPoly::SetCoeff); 21 | Set(target, Nan::New("Polynomial").ToLocalChecked(), tpl->GetFunction()); 22 | } 23 | 24 | private: 25 | explicit WrappedPoly(double a = 0, double b = 0, double c = 0) 26 | : a_(a), b_(b), c_(c) {} 27 | ~WrappedPoly() {} 28 | 29 | static NAN_METHOD(New) { 30 | double a = info[0]->IsUndefined() ? 0 : info[0]->NumberValue(); 31 | double b = info[1]->IsUndefined() ? 0 : info[1]->NumberValue(); 32 | double c = info[2]->IsUndefined() ? 0 : info[2]->NumberValue(); 33 | WrappedPoly* obj = new WrappedPoly(a, b, c); 34 | obj->Wrap(info.This()); 35 | info.GetReturnValue().Set(info.This()); 36 | } 37 | static NAN_METHOD(At) ; 38 | static NAN_METHOD(Roots) ; 39 | 40 | static NAN_GETTER(GetCoeff); 41 | static NAN_SETTER(SetCoeff); 42 | 43 | double a_, b_, c_; 44 | }; 45 | 46 | NAN_METHOD(WrappedPoly::At){ 47 | double x = info[0]->IsUndefined() ? 0 : info[0]->NumberValue(); 48 | WrappedPoly* poly = ObjectWrap::Unwrap(info.Holder()); 49 | double results = x * x * poly->a_ + x * poly->b_ + poly->c_; 50 | info.GetReturnValue().Set(Nan::New(results)); 51 | } 52 | 53 | NAN_METHOD(WrappedPoly::Roots){ 54 | WrappedPoly* poly = ObjectWrap::Unwrap(info.Holder()); 55 | v8::Local roots = Nan::New(); 56 | double desc = poly->b_ * poly->b_ - (4 * poly->a_ * poly->c_); 57 | if (desc >= 0 ) { 58 | double r = (-poly->b_ + sqrt(desc))/(2 * poly->a_); 59 | roots->Set(0,Nan::New(r)); 60 | if ( desc > 0) { 61 | r = (-poly->b_ - sqrt(desc))/(2 * poly->a_); 62 | roots->Set(1,Nan::New(r)); 63 | } 64 | } 65 | info.GetReturnValue().Set(roots); 66 | } 67 | 68 | NAN_GETTER(WrappedPoly::GetCoeff) { 69 | v8::Isolate* isolate = info.GetIsolate(); 70 | WrappedPoly* obj = ObjectWrap::Unwrap(info.This()); 71 | v8::String::Utf8Value s(property); 72 | std::string str(*s, s.length()); 73 | if ( str == "a") info.GetReturnValue().Set(v8::Number::New(isolate, obj->a_)); 74 | else if (str == "b") info.GetReturnValue().Set(v8::Number::New(isolate, obj->b_)); 75 | else if (str == "c") info.GetReturnValue().Set(v8::Number::New(isolate, obj->c_)); 76 | } 77 | 78 | NAN_SETTER(WrappedPoly::SetCoeff) { 79 | WrappedPoly* obj = ObjectWrap::Unwrap(info.This()); 80 | 81 | v8::String::Utf8Value s(property); 82 | std::string str(*s, s.length()); 83 | 84 | if ( str == "a") obj->a_ = value->NumberValue(); 85 | else if (str == "b") obj->b_ = value->NumberValue(); 86 | else if (str == "c") obj->c_ = value->NumberValue(); 87 | } 88 | 89 | void InitPoly(v8::Local exports) { 90 | WrappedPoly::Init(exports); 91 | } 92 | 93 | NODE_MODULE(polynomial, InitPoly) -------------------------------------------------------------------------------- /objectwrap_nan/polynomial.js: -------------------------------------------------------------------------------- 1 | const addon = require('./build/Release/polynomial'); 2 | 3 | var poly = new addon.Polynomial(1, 3, 2); 4 | 5 | console.log(poly.at(4)); 6 | console.log(poly.roots()); 7 | poly.c = 0; 8 | console.log(poly.at(4)); 9 | 10 | console.log(poly); -------------------------------------------------------------------------------- /packaging/addlib/add.cpp: -------------------------------------------------------------------------------- 1 | #include "add.h" 2 | 3 | int sum(int x, int y ) { 4 | return x+y; 5 | } -------------------------------------------------------------------------------- /packaging/addlib/add.h: -------------------------------------------------------------------------------- 1 | int sum(int, int); -------------------------------------------------------------------------------- /packaging/addlib/addlib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "add.h" 3 | using namespace Nan; 4 | using namespace v8; 5 | 6 | NAN_METHOD(Add) { 7 | int a = To(info[0]).FromJust(); 8 | int b = To(info[1]).FromJust(); 9 | 10 | Local retval = Nan::New(sum(a, b)); 11 | info.GetReturnValue().Set(retval); 12 | } 13 | 14 | NAN_MODULE_INIT(Init) { 15 | Nan::Set(target, New("add").ToLocalChecked(), 16 | GetFunction(New(Add)).ToLocalChecked()); 17 | } 18 | 19 | NODE_MODULE(cpp11, Init) -------------------------------------------------------------------------------- /packaging/addlib/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "addlib", 5 | "sources": [ "add.cpp", "addlib.cpp" ], 6 | "include_dirs" : [ 7 | " (http://scottfrees.com/)", 7 | "license": "ISC", 8 | "dependencies": { 9 | "nan": "^2.3.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packaging/cpp11/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "cpp11", 5 | "sources": [ "hello11.cpp" ], 6 | "include_dirs" : [ 7 | " 2 | using namespace Nan; 3 | using namespace v8; 4 | 5 | NAN_METHOD(Add) { 6 | auto sum = [](int x, int y) { return x + y; }; 7 | 8 | int a = To(info[0]).FromJust(); 9 | int b = To(info[1]).FromJust(); 10 | 11 | Local retval = Nan::New(sum(a, b)); 12 | info.GetReturnValue().Set(retval); 13 | } 14 | 15 | NAN_MODULE_INIT(Init) { 16 | Nan::Set(target, New("add").ToLocalChecked(), 17 | GetFunction(New(Add)).ToLocalChecked()); 18 | } 19 | 20 | NODE_MODULE(cpp11, Init) -------------------------------------------------------------------------------- /packaging/cpp11/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cpp11", 3 | "version": "1.0.0", 4 | "main": "./build/Release/cpp11", 5 | "gypfile": true, 6 | "author": "Scott Frees (http://scottfrees.com/)", 7 | "license": "ISC", 8 | "dependencies": { 9 | "nan": "^2.3.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packaging/demo/index.js: -------------------------------------------------------------------------------- 1 | var say = require('hello-world-nodecpp'); 2 | var nansay = require('hello-world-nan-nodecpp'); 3 | var addlib = require('addlib'); 4 | console.log( say.hello() ); 5 | console.log( nansay.hello() ); 6 | 7 | 8 | var sum = addlib.add(5, 2); 9 | console.log(sum); -------------------------------------------------------------------------------- /packaging/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Scott Frees (http://scottfrees.com/)", 6 | "license": "ISC", 7 | "dependencies": { 8 | "hello-world-nodecpp": "^1.0.0", 9 | "hello-world-nan-nodecpp": "file:../hellonan", 10 | "addlib": "^1.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packaging/hello/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "hello_addon", 5 | "sources": [ "hello.cpp" ] 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /packaging/hello/hello.cpp: -------------------------------------------------------------------------------- 1 | // hello.cpp 2 | #include 3 | 4 | using namespace v8; 5 | 6 | void Method(const FunctionCallbackInfo& args) { 7 | Isolate* isolate = args.GetIsolate(); 8 | Local retval = String::NewFromUtf8(isolate, "hello"); 9 | args.GetReturnValue().Set(retval); 10 | } 11 | 12 | void init(Local exports) { 13 | NODE_SET_METHOD(exports, "hello", Method); 14 | } 15 | 16 | NODE_MODULE(hello_addon, init) 17 | 18 | -------------------------------------------------------------------------------- /packaging/hello/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world-nodecpp", 3 | "version": "1.0.0", 4 | "main": "./build/Release/hello_addon", 5 | "gypfile": true, 6 | "author": "Scott Frees (http://scottfrees.com/)", 7 | "license": "ISC" 8 | } 9 | -------------------------------------------------------------------------------- /packaging/hellonan/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "hello_nan_addon", 5 | "sources": [ "hello_nan.cpp" ], 6 | "include_dirs" : [ 7 | " 2 | using namespace Nan; 3 | using namespace v8; 4 | 5 | NAN_METHOD(Method) { 6 | info.GetReturnValue().Set(New("nan hello").ToLocalChecked()); 7 | } 8 | 9 | NAN_MODULE_INIT(Init) { 10 | Nan::Set(target, New("hello").ToLocalChecked(), 11 | GetFunction(New(Method)).ToLocalChecked()); 12 | } 13 | 14 | NODE_MODULE(hello_nan_addon, Init) 15 | 16 | -------------------------------------------------------------------------------- /packaging/hellonan/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world-nan-nodecpp", 3 | "version": "1.0.0", 4 | "main": "./build/Release/hello_nan_addon", 5 | "gypfile": true, 6 | "author": "Scott Frees (http://scottfrees.com/)", 7 | "license": "ISC", 8 | "dependencies": { 9 | "nan": "^2.3.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /prebuilt/addon/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "native_rt", 5 | "sources": [ "native-rt.cc" ], 6 | "conditions":[ 7 | ["OS=='linux'", { 8 | "sources": [ "native-rt_linux.cc" ] 9 | }], 10 | ["OS=='mac'", { 11 | "sources": [ "native-rt_mac.cc" ] 12 | }], 13 | ["OS=='win'", { 14 | "sources": [ "native-rt_win.cc" ] 15 | }] 16 | ], 17 | "include_dirs" : [ 18 | " 2 | using namespace Nan; 3 | using namespace v8; 4 | 5 | #include "native-rt.h" 6 | 7 | NAN_METHOD(now) { 8 | double time_now = native_now(); 9 | Local retval = Nan::New(time_now); 10 | info.GetReturnValue().Set(retval); 11 | } 12 | 13 | NAN_MODULE_INIT(Init) { 14 | Nan::Set(target, New("now").ToLocalChecked(), 15 | GetFunction(New(now)).ToLocalChecked()); 16 | } 17 | 18 | NODE_MODULE(native_rt, Init) -------------------------------------------------------------------------------- /prebuilt/addon/native-rt.h: -------------------------------------------------------------------------------- 1 | double native_now(); -------------------------------------------------------------------------------- /prebuilt/addon/native-rt_linux.cc: -------------------------------------------------------------------------------- 1 | // Platform specific (linux) 2 | #include 3 | #include 4 | #include 5 | 6 | double native_now() { 7 | struct timespec ts; 8 | #if defined(CLOCK_MONOTONIC_RAW) 9 | const clockid_t id = CLOCK_MONOTONIC_RAW; 10 | #elif defined(CLOCK_REALTIME) 11 | const clockid_t id = CLOCK_REALTIME; 12 | #else 13 | const clockid_t id = (clockid_t)-1; 14 | #endif 15 | if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 ){ 16 | double time_now = (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0; 17 | return time_now; 18 | } 19 | return 0; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /prebuilt/addon/native-rt_mac.cc: -------------------------------------------------------------------------------- 1 | // Platform specific (macOS) 2 | #include 3 | #include 4 | 5 | double native_now() { 6 | static double timeConvert = 0.0; 7 | if ( timeConvert == 0.0 ) 8 | { 9 | mach_timebase_info_data_t timeBase; 10 | (void)mach_timebase_info( &timeBase ); 11 | timeConvert = (double)timeBase.numer / 12 | (double)timeBase.denom / 13 | 1000000000.0; 14 | } 15 | double time_now = (double)mach_absolute_time( ) * timeConvert; 16 | return time_now; 17 | } 18 | -------------------------------------------------------------------------------- /prebuilt/addon/native-rt_win.cc: -------------------------------------------------------------------------------- 1 | // Platform specific (windows) 2 | #include 3 | 4 | double native_now() 5 | { 6 | FILETIME tm; 7 | ULONGLONG t; 8 | #if defined(NTDDI_WIN8) && NTDDI_VERSION >= NTDDI_WIN8 9 | /* Windows 8, Windows Server 2012 and later. ---------------- */ 10 | GetSystemTimePreciseAsFileTime(&tm); 11 | #else 12 | /* Windows 2000 and later. ---------------------------------- */ 13 | GetSystemTimeAsFileTime(&tm); 14 | #endif 15 | t = ((ULONGLONG)tm.dwHighDateTime << 32) | (ULONGLONG)tm.dwLowDateTime; 16 | double time_now = (double)t / 10000000.0; 17 | return time_now; 18 | } -------------------------------------------------------------------------------- /prebuilt/addon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "native_rt", 3 | "version": "1.0.1", 4 | "description": "Example for using node-pre-gyp for cross-platform binaries", 5 | "gypfile": true, 6 | "main": "./index.js", 7 | "author": "Scott Frees (http://scottfrees.com/)", 8 | "license": "MIT", 9 | "dependencies": { 10 | "aws-sdk": "2.7.27", 11 | "nan": "^2.3.3", 12 | "node-pre-gyp": "0.6.32" 13 | }, 14 | "binary": { 15 | "module_name": "native_rt", 16 | "module_path": "./lib/binding/{configuration}/{node_abi}-{platform}-{arch}/", 17 | "remote_path": "./{module_name}/v{version}/{configuration}/", 18 | "package_name": "{module_name}-v{version}-{node_abi}-{platform}-{arch}.tar.gz", 19 | "host": "https://nodeaddons.s3-us-west-2.amazonaws.com" 20 | }, 21 | "scripts": { 22 | "preinstall": "npm install node-pre-gyp", 23 | "install": "node-pre-gyp install --fallback-to-build" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /prebuilt/example/index.js: -------------------------------------------------------------------------------- 1 | var rt = require('native_rt'); 2 | 3 | var start = rt.now(); 4 | setTimeout(function() { 5 | let end = rt.now(); 6 | console.log(end - start); 7 | }, 1000) -------------------------------------------------------------------------------- /prebuilt/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Scott Frees (http://scottfrees.com/)", 6 | "license": "MIT", 7 | "dependencies": { 8 | "native_rt": "file:../addon" 9 | } 10 | } -------------------------------------------------------------------------------- /primes/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "primes", 5 | "sources": [ "primes.cpp" ], 6 | "cflags": ["-Wall", "-std=c++11"], 7 | "include_dirs" : [" (http://scottfrees.com/)", 10 | "license": "ISC", 11 | "dependencies": { 12 | "nan": "^2.3.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /primes/primes.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | using namespace Nan; 9 | using namespace v8; 10 | 11 | 12 | 13 | void find_primes(int limit, vector primes) { 14 | std::vector is_prime(limit, true); 15 | for (int n = 2; n < limit; n++ ) { 16 | double p = n / (limit-n); 17 | if (is_prime[n] ) primes.push_back(n); 18 | for (int i = n * n; i < limit; i+= n) { 19 | is_prime[i] = false; 20 | } 21 | } 22 | } 23 | class PrimeWorker : public AsyncWorker { 24 | public: 25 | PrimeWorker(Callback * callback, int limit) 26 | : AsyncWorker(callback), limit(limit) { 27 | 28 | } 29 | // Executes in worker thread 30 | void Execute() { 31 | find_primes(limit, primes); 32 | } 33 | // Executes in event loop 34 | void HandleOKCallback () { 35 | Local results = New(primes.size()); 36 | for ( unsigned int i = 0; i < primes.size(); i++ ) { 37 | Nan::Set(results, i, New(primes[i])); 38 | } 39 | Local argv[] = { results }; 40 | callback->Call(1, argv); 41 | } 42 | private: 43 | int limit; 44 | vector primes; 45 | }; 46 | 47 | class PrimeProgressWorker : public AsyncProgressWorker { 48 | public: 49 | PrimeProgressWorker(Callback * callback, Callback * progress, int limit) 50 | : AsyncProgressWorker(callback), progress(progress), limit(limit) { 51 | 52 | } 53 | // Executes in worker thread 54 | void Execute(const AsyncProgressWorker::ExecutionProgress& progress) { 55 | std::vector is_prime(limit, true); 56 | for (int n = 2; n < limit; n++ ) { 57 | double p = (100.0 * n) / limit; 58 | progress.Send(reinterpret_cast(&p), sizeof(double)); 59 | if (is_prime[n] ) primes.push_back(n); 60 | for (int i = n * n; i < limit; i+= n) { 61 | is_prime[i] = false; 62 | } 63 | std::this_thread::sleep_for(chrono::milliseconds(100)); 64 | } 65 | } 66 | // Executes in event loop 67 | void HandleOKCallback () { 68 | Local results = New(primes.size()); 69 | for ( unsigned int i = 0; i < primes.size(); i++ ) { 70 | Nan::Set(results, i, New(primes[i])); 71 | } 72 | Local argv[] = { results }; 73 | callback->Call(1, argv); 74 | } 75 | 76 | void HandleProgressCallback(const char *data, size_t size) { 77 | // Required, this is not created automatically 78 | Nan::HandleScope scope; 79 | 80 | Local argv[] = { 81 | New(*reinterpret_cast(const_cast(data))) 82 | }; 83 | progress->Call(1, argv); 84 | } 85 | private: 86 | Callback *progress; 87 | int limit; 88 | vector primes; 89 | }; 90 | 91 | NAN_METHOD(Primes) { 92 | int limit = To(info[0]).FromJust(); 93 | Callback *callback = new Callback(info[1].As()); 94 | 95 | AsyncQueueWorker(new PrimeWorker(callback, limit)); 96 | } 97 | 98 | NAN_METHOD(PrimesProgress) { 99 | int limit = To(info[0]).FromJust(); 100 | Callback *callback = new Callback(info[1].As()); 101 | Callback *progress = new Callback(info[2].As()); 102 | 103 | AsyncQueueWorker(new PrimeProgressWorker(callback, progress, limit)); 104 | } 105 | 106 | /* 107 | NAN_METHOD(Primes) { 108 | int limit = info[0]->IntegerValue(); 109 | Callback *callback = new Callback(info[1].As()); 110 | 111 | vector primes; 112 | find_primes(limit, primes); 113 | 114 | Local results = New(primes.size()); 115 | for ( unsigned int i = 0; i < primes.size(); i++ ) { 116 | Nan::Set(results, i, New(primes[i])); 117 | } 118 | 119 | Local argv[] = { results }; 120 | callback->Call(1, argv); 121 | } 122 | */ 123 | 124 | NAN_MODULE_INIT(Init) { 125 | Nan::Set(target, New("primes").ToLocalChecked(), 126 | GetFunction(New(Primes)).ToLocalChecked()); 127 | Nan::Set(target, New("primes_progress").ToLocalChecked(), 128 | GetFunction(New(PrimesProgress)).ToLocalChecked()); 129 | } 130 | 131 | NODE_MODULE(primes, Init) -------------------------------------------------------------------------------- /primes/primes.js: -------------------------------------------------------------------------------- 1 | const addon = require('./build/Release/primes'); 2 | 3 | addon.primes_progress(20, function (primes) { 4 | // prints 2, 3, 5, 7, 11, 13, 17, 19 5 | console.log("Primes less than 20 = " + primes); 6 | }, function(progress) { 7 | console.log(progress + "% complete"); 8 | }); -------------------------------------------------------------------------------- /quickstart/.vscode/.browse.VC.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freezer333/nodecpp-demo/9130c01631ba20048c3fffc3632eadcb94971724/quickstart/.vscode/.browse.VC.db -------------------------------------------------------------------------------- /quickstart/addon/addon_source.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using namespace std; 5 | using namespace Nan; 6 | using namespace v8; 7 | 8 | NAN_METHOD(PassNumber) { 9 | Nan::Maybe value = Nan::To(info[0]); 10 | Local retval = Nan::New(value.FromJust() + 42); 11 | info.GetReturnValue().Set(retval); 12 | } 13 | 14 | NAN_METHOD(PassInteger) { 15 | if ( info.Length() < 1 ) { 16 | return; 17 | } 18 | if ( !info[0]->IsInt32()) { 19 | return; 20 | } 21 | int value = info[0]->IntegerValue(); 22 | Local retval = Nan::New(value + 42); 23 | info.GetReturnValue().Set(retval); 24 | } 25 | 26 | NAN_METHOD(PassBoolean) { 27 | if ( info.Length() < 1 ) { 28 | return; 29 | } 30 | if ( !info[0]->IsBoolean()) { 31 | return; 32 | } 33 | bool value = info[0]->BooleanValue(); 34 | Local retval = Nan::New(!value); 35 | info.GetReturnValue().Set(retval); 36 | } 37 | 38 | 39 | NAN_METHOD(PassString) { 40 | if ( info.Length() < 1 ) { 41 | return; 42 | } 43 | if ( !info[0]->IsString()) { 44 | return; 45 | } 46 | v8::String::Utf8Value val(info[0]->ToString()); 47 | 48 | std::string str (*val, val.length()); 49 | std::reverse(str.begin(), str.end()); 50 | 51 | info.GetReturnValue().Set(Nan::New(str.c_str()).ToLocalChecked()); 52 | } 53 | 54 | NAN_METHOD(PassObject) { 55 | if ( info.Length() > 0 ) { 56 | Local input = info[0]->ToObject(); 57 | 58 | // Make property names to access the input object 59 | Local x_prop = Nan::New("x").ToLocalChecked(); 60 | Local y_prop = Nan::New("y").ToLocalChecked(); 61 | Local sum_prop = Nan::New("sum").ToLocalChecked(); 62 | Local product_prop = Nan::New("product").ToLocalChecked(); 63 | 64 | // create the return object 65 | Local retval = Nan::New(); 66 | 67 | // pull x and y out of the input. We'll get NaN if these weren't set, 68 | // or if x / y aren't able to be converted to numbers. 69 | double x = Nan::Get(input, x_prop).ToLocalChecked()->NumberValue(); 70 | double y = Nan::Get(input, y_prop).ToLocalChecked()->NumberValue(); 71 | 72 | // set the properties on the return object 73 | Nan::Set(retval, sum_prop, Nan::New(x+y)); 74 | Nan::Set(retval, product_prop, Nan::New(x*y)); 75 | 76 | info.GetReturnValue().Set(retval); 77 | } 78 | } 79 | 80 | // Increment each value in the array parameter, 81 | // Return a new array with the squares of the original 82 | // array and a 'sum_of_squares' property. 83 | NAN_METHOD(IncrementArray) { 84 | Local array = Local::Cast(info[0]); 85 | 86 | Local ss_prop = Nan::New("sum_of_squares").ToLocalChecked(); 87 | Local squares = New(array->Length()); 88 | double ss = 0; 89 | 90 | for (unsigned int i = 0; i < array->Length(); i++ ) { 91 | if (Nan::Has(array, i).FromJust()) { 92 | // get data from a particular index 93 | double value = Nan::Get(array, i).ToLocalChecked()->NumberValue(); 94 | 95 | // set a particular index - note the array parameter 96 | // is mutable 97 | Nan::Set(array, i, Nan::New(value + 1)); 98 | Nan::Set(squares, i, Nan::New(value * value)); 99 | ss += value*value; 100 | } 101 | } 102 | // set a non index property on the returned array. 103 | Nan::Set(squares, ss_prop, Nan::New(ss)); 104 | info.GetReturnValue().Set(squares); 105 | } 106 | 107 | NAN_METHOD(AddArray) { 108 | if ( info.Length() > 0 ) { 109 | Local input = info[0]->ToObject(); 110 | Local a_prop = Nan::New("a").ToLocalChecked(); 111 | Local b_prop = Nan::New("b").ToLocalChecked(); 112 | 113 | double a = Nan::Get(input, a_prop).ToLocalChecked()->NumberValue(); 114 | Local b = Local::Cast(Nan::Get(input, b_prop).ToLocalChecked()); 115 | 116 | for (unsigned int i = 0; i < b->Length(); i++ ) { 117 | if (Nan::Has(b, i).FromJust()) { 118 | // get data from a particular index 119 | double value = Nan::Get(b, i).ToLocalChecked()->NumberValue(); 120 | 121 | // set a particular index - note the array parameter 122 | // is mutable 123 | Nan::Set(b, i, Nan::New(value + a)); 124 | } 125 | } 126 | } 127 | } 128 | 129 | NAN_MODULE_INIT(Init) { 130 | Nan::Set(target, New("pass_number").ToLocalChecked(), 131 | GetFunction(New(PassNumber)).ToLocalChecked()); 132 | Nan::Set(target, New("pass_integer").ToLocalChecked(), 133 | GetFunction(New(PassInteger)).ToLocalChecked()); 134 | Nan::Set(target, New("pass_boolean").ToLocalChecked(), 135 | GetFunction(New(PassBoolean)).ToLocalChecked()); 136 | Nan::Set(target, New("pass_string").ToLocalChecked(), 137 | GetFunction(New(PassString)).ToLocalChecked()); 138 | 139 | Nan::Set(target, New("pass_object").ToLocalChecked(), 140 | GetFunction(New(PassObject)).ToLocalChecked()); 141 | 142 | Nan::Set(target, New("pass_array").ToLocalChecked(), 143 | GetFunction(New(IncrementArray)).ToLocalChecked()); 144 | 145 | Nan::Set(target, New("add_array").ToLocalChecked(), 146 | GetFunction(New(AddArray)).ToLocalChecked()); 147 | } 148 | 149 | NODE_MODULE(my_addon, Init) -------------------------------------------------------------------------------- /quickstart/addon/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "my_addon", 5 | "sources": [ "addon_source.cc" ], 6 | "include_dirs" : [ 7 | " 3 | #include 4 | 5 | bool operator<(const sample &s1, const sample &s2) { 6 | return s1.rainfall < s2.rainfall; 7 | } 8 | 9 | double avg_rainfall(location & loc) { 10 | double total = 0; 11 | for (const auto &sample : loc.samples) { 12 | total += sample.rainfall; 13 | } 14 | return total / loc.samples.size(); 15 | } 16 | 17 | rain_result calc_rain_stats(location &loc) { 18 | rain_result result; 19 | double ss = 0; 20 | double total = 0; 21 | 22 | result.n = loc.samples.size(); 23 | 24 | for (const auto &sample : loc.samples) { 25 | total += sample.rainfall; 26 | } 27 | result.mean = total / loc.samples.size(); 28 | 29 | for (const auto &sample : loc.samples) { 30 | ss += pow(sample.rainfall - result.mean, 2); 31 | } 32 | result.standard_deviation = sqrt(ss/(result.n-1)); 33 | 34 | std::sort(loc.samples.begin(), loc.samples.end()); 35 | if (result.n %2 == 0) { 36 | result.median = (loc.samples[result.n / 2 - 1].rainfall + loc.samples[result.n / 2].rainfall) / 2; 37 | } 38 | else { 39 | result.median = loc.samples[result.n / 2].rainfall; 40 | } 41 | return result; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /rainfall/cpp/rainfall.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | 5 | class sample { 6 | public: 7 | sample () { 8 | date = ""; rainfall = 0; 9 | } 10 | sample (string d, double r) { 11 | date = d; 12 | rainfall = r; 13 | } 14 | 15 | string date; 16 | double rainfall; 17 | }; 18 | 19 | // added for median calculation (sorting) 20 | bool operator<(const sample &s1, const sample &s2); 21 | 22 | class location { 23 | public: 24 | double longitude; 25 | double latitude; 26 | vector samples; 27 | }; 28 | 29 | 30 | class rain_result { 31 | public: 32 | float median; 33 | float mean; 34 | float standard_deviation; 35 | int n; 36 | }; 37 | 38 | double avg_rainfall(location & loc); 39 | 40 | rain_result calc_rain_stats(location &loc); 41 | -------------------------------------------------------------------------------- /rainfall/cpp/rainfall_node.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "rainfall.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace v8; 13 | 14 | location unpack_location(Isolate * , const Handle sample_obj); 15 | sample unpack_sample(Isolate * , const Handle ); 16 | void pack_rain_result(v8::Isolate* isolate, v8::Local & target, rain_result & result); 17 | 18 | 19 | /////////////////////////////////////////////////////////////////// 20 | // Part 4 - Callbacks 21 | /////////////////////////////////////////////////////////////////// 22 | 23 | struct Work { 24 | uv_work_t request; 25 | Persistent callback; 26 | 27 | std::vector locations; 28 | std::vector results; 29 | }; 30 | 31 | // called by libuv worker in separate thread 32 | static void WorkAsync(uv_work_t *req) 33 | { 34 | Work *work = static_cast(req->data); 35 | 36 | // this is the worker thread, lets build up the results 37 | // allocated results from the heap because we'll need 38 | // to access in the event loop later to send back 39 | work->results.resize(work->locations.size()); 40 | std::transform(work->locations.begin(), work->locations.end(), work->results.begin(), calc_rain_stats); 41 | 42 | 43 | // that wasn't really that long of an operation, so lets pretend it took longer... 44 | std::this_thread::sleep_for(chrono::seconds(3)); 45 | } 46 | 47 | // called by libuv in event loop when async function completes 48 | static void WorkAsyncComplete(uv_work_t *req,int status) 49 | { 50 | Isolate * isolate = Isolate::GetCurrent(); 51 | 52 | // Fix for Node 4.x - thanks to https://github.com/nwjs/blink/commit/ecda32d117aca108c44f38c8eb2cb2d0810dfdeb 53 | v8::HandleScope handleScope(isolate); 54 | 55 | Local result_list = Array::New(isolate); 56 | Work *work = static_cast(req->data); 57 | 58 | // the work has been done, and now we pack the results 59 | // vector into a Local array on the event-thread's stack. 60 | 61 | for (unsigned int i = 0; i < work->results.size(); i++ ) { 62 | Local result = Object::New(isolate); 63 | pack_rain_result(isolate, result, work->results[i]); 64 | result_list->Set(i, result); 65 | } 66 | 67 | // set up return arguments 68 | Handle argv[] = { Null(isolate) , result_list }; 69 | 70 | // execute the callback 71 | // https://stackoverflow.com/questions/13826803/calling-javascript-function-from-a-c-callback-in-v8/28554065#28554065 72 | Local::New(isolate, work->callback)->Call(isolate->GetCurrentContext()->Global(), 2, argv); 73 | 74 | // Free up the persistent function callback 75 | work->callback.Reset(); 76 | delete work; 77 | 78 | } 79 | 80 | void CalculateResultsAsync(const v8::FunctionCallbackInfo&args) { 81 | Isolate* isolate = args.GetIsolate(); 82 | 83 | Work * work = new Work(); 84 | work->request.data = work; 85 | 86 | // extract each location (its a list) and store it in the work package 87 | // locations is on the heap, accessible in the libuv threads 88 | Local input = Local::Cast(args[0]); 89 | unsigned int num_locations = input->Length(); 90 | for (unsigned int i = 0; i < num_locations; i++) { 91 | work->locations.push_back(unpack_location(isolate, Local::Cast(input->Get(i)))); 92 | } 93 | 94 | // store the callback from JS in the work package so we can 95 | // invoke it later 96 | Local callback = Local::Cast(args[1]); 97 | work->callback.Reset(isolate, callback); 98 | 99 | // kick of the worker thread 100 | uv_queue_work(uv_default_loop(),&work->request,WorkAsync,WorkAsyncComplete); 101 | 102 | 103 | args.GetReturnValue().Set(Undefined(isolate)); 104 | 105 | } 106 | 107 | void CalculateResultsSync(const v8::FunctionCallbackInfo&args) { 108 | Isolate* isolate = args.GetIsolate(); 109 | std::vector locations; 110 | std::vector results; 111 | 112 | // extract each location (its a list) 113 | Local input = Local::Cast(args[0]); 114 | unsigned int num_locations = input->Length(); 115 | for (unsigned int i = 0; i < num_locations; i++) { 116 | locations.push_back(unpack_location(isolate, Local::Cast(input->Get(i)))); 117 | } 118 | 119 | // Build vector of rain_results 120 | results.resize(locations.size()); 121 | std::transform(locations.begin(), locations.end(), results.begin(), calc_rain_stats); 122 | 123 | 124 | // Convert the rain_results into Objects for return 125 | Local result_list = Array::New(isolate); 126 | for (unsigned int i = 0; i < results.size(); i++ ) { 127 | Local result = Object::New(isolate); 128 | pack_rain_result(isolate, result, results[i]); 129 | result_list->Set(i, result); 130 | } 131 | 132 | 133 | Local callback = Local::Cast(args[1]); 134 | Handle argv[] = { result_list }; 135 | callback->Call(isolate->GetCurrentContext()->Global(), 1, argv); 136 | 137 | std::cerr << "Returning from C++ now" << std::endl; 138 | 139 | args.GetReturnValue().Set(Undefined(isolate)); 140 | } 141 | 142 | /////////////////////////////////////////////////////////////////// 143 | // Part 3 - Lists and Nested Objects 144 | /////////////////////////////////////////////////////////////////// 145 | void CalculateResults(const v8::FunctionCallbackInfo&args) { 146 | Isolate* isolate = args.GetIsolate(); 147 | std::vector locations; 148 | std::vector results; 149 | 150 | // extract each location (its a list) 151 | Local input = Local::Cast(args[0]); 152 | unsigned int num_locations = input->Length(); 153 | for (unsigned int i = 0; i < num_locations; i++) { 154 | locations.push_back(unpack_location(isolate, Local::Cast(input->Get(i)))); 155 | } 156 | 157 | // Build vector of rain_results 158 | results.resize(locations.size()); 159 | std::transform(locations.begin(), locations.end(), results.begin(), calc_rain_stats); 160 | 161 | 162 | // Convert the rain_results into Objects for return 163 | Local result_list = Array::New(isolate); 164 | for (unsigned int i = 0; i < results.size(); i++ ) { 165 | Local result = Object::New(isolate); 166 | pack_rain_result(isolate, result, results[i]); 167 | result_list->Set(i, result); 168 | } 169 | 170 | // Return the list 171 | args.GetReturnValue().Set(result_list); 172 | } 173 | 174 | 175 | /////////////////////////////////////////////////////////////////// 176 | // Part 2 - Returning objects 177 | /////////////////////////////////////////////////////////////////// 178 | void pack_rain_result(v8::Isolate* isolate, v8::Local & target, rain_result & result){ 179 | target->Set(String::NewFromUtf8(isolate, "mean"), Number::New(isolate, result.mean)); 180 | target->Set(String::NewFromUtf8(isolate, "median"), Number::New(isolate, result.median)); 181 | target->Set(String::NewFromUtf8(isolate, "standard_deviation"), Number::New(isolate, result.standard_deviation)); 182 | target->Set(String::NewFromUtf8(isolate, "n"), Integer::New(isolate, result.n)); 183 | } 184 | 185 | void RainfallData(const v8::FunctionCallbackInfo& args) { 186 | Isolate* isolate = args.GetIsolate(); 187 | 188 | location loc = unpack_location(isolate, Handle::Cast(args[0])); 189 | rain_result result = calc_rain_stats(loc); 190 | 191 | Local obj = Object::New(isolate); 192 | pack_rain_result(isolate, obj, result); 193 | 194 | args.GetReturnValue().Set(obj); 195 | } 196 | 197 | 198 | /////////////////////////////////////////////////////////////////// 199 | // Part 1 - Receiving JSON objects 200 | /////////////////////////////////////////////////////////////////// 201 | 202 | sample unpack_sample(Isolate * isolate, const Handle sample_obj) { 203 | sample s; 204 | Handle date_Value = sample_obj->Get(String::NewFromUtf8(isolate, "date")); 205 | Handle rainfall_Value = sample_obj->Get(String::NewFromUtf8(isolate, "rainfall")); 206 | 207 | v8::String::Utf8Value utfValue(date_Value); 208 | s.date = std::string(*utfValue, utfValue.length()); 209 | 210 | // Unpack the numeric rainfall amount directly from V8 value 211 | s.rainfall = rainfall_Value->NumberValue(); 212 | return s; 213 | } 214 | 215 | 216 | 217 | location unpack_location(Isolate * isolate, const Handle location_obj) { 218 | location loc; 219 | 220 | Handle lat_Value = location_obj->Get(String::NewFromUtf8(isolate,"latitude")); 221 | Handle lon_Value = location_obj->Get(String::NewFromUtf8(isolate,"longitude")); 222 | loc.latitude = lat_Value->NumberValue(); 223 | loc.longitude = lon_Value->NumberValue(); 224 | 225 | Handle array = Handle::Cast(location_obj->Get(String::NewFromUtf8(isolate,"samples"))); 226 | int sample_count = array->Length(); 227 | for ( int i = 0; i < sample_count; i++ ) { 228 | sample s = unpack_sample(isolate, Handle::Cast(array->Get(i))); 229 | loc.samples.push_back(s); 230 | } 231 | return loc; 232 | } 233 | 234 | void AvgRainfall(const v8::FunctionCallbackInfo& args) { 235 | Isolate* isolate = args.GetIsolate(); 236 | 237 | location loc = unpack_location(isolate, Handle::Cast(args[0])); 238 | double avg = avg_rainfall(loc); 239 | 240 | Local retval = v8::Number::New(isolate, avg); 241 | args.GetReturnValue().Set(retval); 242 | } 243 | 244 | 245 | 246 | void init(Handle exports, Handle module) { 247 | NODE_SET_METHOD(exports, "avg_rainfall", AvgRainfall); 248 | NODE_SET_METHOD(exports, "data_rainfall", RainfallData); 249 | NODE_SET_METHOD(exports, "calculate_results", CalculateResults); 250 | NODE_SET_METHOD(exports, "calculate_results_sync", CalculateResultsSync); 251 | NODE_SET_METHOD(exports, "calculate_results_async", CalculateResultsAsync); 252 | 253 | } 254 | 255 | NODE_MODULE(rainfall, init) 256 | -------------------------------------------------------------------------------- /rainfall/cpp/rainfall_test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "rainfall.h" 3 | using namespace std; 4 | 5 | int main() { 6 | location loc; 7 | 8 | loc.latitude = 40.71; 9 | loc.longitude = -74.01; 10 | 11 | loc.samples.push_back(sample("2014-11-30", 1.00)); 12 | loc.samples.push_back(sample("2014-12-01", 1.50)); 13 | loc.samples.push_back(sample("2014-12-02", 0.25)); 14 | 15 | cout << "Average rainfall = " << avg_rainfall(loc) << "cm" << endl; 16 | } 17 | -------------------------------------------------------------------------------- /rainfall/rainfall.js: -------------------------------------------------------------------------------- 1 | var rainfall = require("./cpp/build/Release/rainfall"); 2 | var location = { 3 | latitude : 40.71, longitude : -74.01, 4 | samples : [ 5 | { date : "2015-06-07", rainfall : 2.1 }, 6 | { date : "2015-06-14", rainfall : 0.5}, 7 | { date : "2015-06-21", rainfall : 1.5}, 8 | { date : "2015-06-28", rainfall : 1.3}, 9 | { date : "2015-07-05", rainfall : 0.9} 10 | ] }; 11 | 12 | // utility printing 13 | var print_rain_results = function(results) { 14 | var i = 0; 15 | results.forEach(function(result){ 16 | console.log("Result for Location " + i); 17 | console.log("--------------------------"); 18 | console.log("\tLatitude: " + locations[i].latitude.toFixed(2)); 19 | console.log("\tLongitude: " + locations[i].longitude.toFixed(2)); 20 | console.log("\tMean Rainfall: " + result.mean.toFixed(2) + "cm"); 21 | console.log("\tMedian Rainfall: " + result.median.toFixed(2) + "cm"); 22 | console.log("\tStandard Dev.: " + result.standard_deviation.toFixed(2) + "cm"); 23 | console.log("\tNumber Samples: " + result.n); 24 | console.log(); 25 | i++; 26 | }); 27 | } 28 | 29 | // Part 1 30 | console.log("Average rain fall = " + rainfall.avg_rainfall(location) + "cm"); 31 | 32 | // Part 2 33 | console.log("Rainfall Data = " + JSON.stringify(rainfall.data_rainfall(location))); 34 | 35 | // Part 3 36 | 37 | var makeup = function(max) { 38 | return Math.round(max * Math.random() * 100)/100; 39 | } 40 | 41 | var locations = [] 42 | for (var i = 0; i < 10; i++ ) { 43 | var loc = { 44 | latitude: makeup(180), 45 | longitude: makeup(180), 46 | samples : [ 47 | {date: "2015-07-20", rainfall: makeup(3)}, 48 | {date: "2015-07-21", rainfall: makeup(3)}, 49 | {date: "2015-07-22", rainfall: makeup(3)}, 50 | {date: "2015-07-23", rainfall: makeup(3)} 51 | ] 52 | } 53 | locations.push(loc); 54 | } 55 | 56 | var results = rainfall.calculate_results(locations); 57 | print_rain_results(results); 58 | 59 | 60 | // Part 4 - calling asynchronous c++ addon 61 | rainfall.calculate_results_async(locations, 62 | function(err, result) { 63 | if (err ) { 64 | console.log(err); 65 | } 66 | else { 67 | print_rain_results(result); 68 | } 69 | 70 | }); 71 | 72 | console.log("Async results probably still not here yet...") 73 | -------------------------------------------------------------------------------- /rainfall/rainfall.json: -------------------------------------------------------------------------------- 1 | { 2 | "locations" : [ 3 | { 4 | "latitude" : "40.71", 5 | "longitude" : "-74.01", 6 | "samples" : [ 7 | { 8 | "date" : "2014-06-07", 9 | "rainfall" : "2" 10 | }, 11 | { 12 | "date" : "2014-08-12", 13 | "rainfall" : "0.5" 14 | }, 15 | { 16 | "date" : "2014-09-29", 17 | "rainfall" : "1.25" 18 | } 19 | ] 20 | }, 21 | { 22 | "latitude" : "42.35", 23 | "longitude" : "-71.06", 24 | "samples" : [ 25 | { 26 | "date" : "2014-03-03", 27 | "rainfall" : "1.75" 28 | }, 29 | { 30 | "date" : "2014-05-16", 31 | "rainfall" : "0.25" 32 | }, 33 | { 34 | "date" : "2014-03-18", 35 | "rainfall" : "2.25" 36 | } 37 | ] 38 | } 39 | ] 40 | 41 | } 42 | -------------------------------------------------------------------------------- /streaming/LICENSE.txt: -------------------------------------------------------------------------------- 1 | This software is released under the MIT license: 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /streaming/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const emitStream = require('emit-stream'); 4 | const through = require('through'); 5 | const EventEmitter = require('events'); 6 | 7 | var make_stream = function(cpp_entry_point, opts) { 8 | const stream2node = require(cpp_entry_point); 9 | var emitter = new EventEmitter(); 10 | 11 | var worker = new stream2node.StreamingWorker( 12 | function(event, value){ 13 | emitter.emit(event, value); 14 | }, 15 | function () { 16 | emitter.emit("close"); 17 | }, 18 | function(error) { 19 | emitter.emit("error", error); 20 | }, 21 | opts); 22 | 23 | var sw = {}; 24 | 25 | 26 | sw.from = emitter; 27 | sw.from.stream = function() { 28 | return emitStream(sw.from).pipe( 29 | through(function (data) { 30 | if ( data[0] == "close"){ 31 | this.end(); 32 | } 33 | else { 34 | this.queue(data); 35 | } 36 | })); 37 | } 38 | 39 | sw.to = { 40 | emit : function(name, data) { 41 | worker.sendToAddon(name, data); 42 | }, 43 | stream : function(name, end) { 44 | var input = through(function write(data) { 45 | if (Array.isArray(data)) { 46 | if ( data[0] == "close"){ 47 | this.end(); 48 | } 49 | else { 50 | sw.to.emit(data[0], data[1]); 51 | } 52 | } 53 | else { 54 | sw.to.emit(name, data); 55 | } 56 | }, 57 | end); 58 | return input; 59 | } 60 | } 61 | sw.close = function (){ 62 | worker.closeInput(); 63 | } 64 | 65 | 66 | return sw; 67 | } 68 | 69 | module.exports = make_stream; 70 | -------------------------------------------------------------------------------- /streaming/dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "streaming-worker", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "emit-stream": "^0.1.2", 6 | "nan": "*", 7 | "through": "^2.3.8" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /streaming/dist/streaming-worker.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace Nan; 13 | using namespace std; 14 | 15 | template 16 | class PCQueue 17 | { 18 | public: 19 | void write(Data data) 20 | { 21 | std::unique_lock locker(mu); 22 | buffer_.push_back(data); 23 | locker.unlock(); 24 | cond.notify_all(); 25 | return; 26 | } 27 | Data read() 28 | { 29 | std::unique_lock locker(mu); 30 | cond.wait(locker, [this]() { return buffer_.size() > 0; }); 31 | Data back = buffer_.front(); 32 | buffer_.pop_front(); 33 | locker.unlock(); 34 | cond.notify_all(); 35 | return back; 36 | } 37 | void readAll(std::deque &target) 38 | { 39 | std::unique_lock locker(mu); 40 | std::copy(buffer_.begin(), buffer_.end(), std::back_inserter(target)); 41 | buffer_.clear(); 42 | locker.unlock(); 43 | } 44 | PCQueue() {} 45 | 46 | private: 47 | std::mutex mu; 48 | std::condition_variable cond; 49 | std::deque buffer_; 50 | }; 51 | 52 | class Message 53 | { 54 | public: 55 | string name; 56 | string data; 57 | Message(string name, string data) : name(name), data(data) {} 58 | }; 59 | 60 | class StreamingWorker : public AsyncProgressWorker 61 | { 62 | public: 63 | StreamingWorker( 64 | Callback *progress, 65 | Callback *callback, 66 | Callback *error_callback) 67 | : AsyncProgressWorker(callback), progress(progress), error_callback(error_callback) 68 | { 69 | input_closed = false; 70 | } 71 | ~StreamingWorker() 72 | { 73 | delete progress; 74 | delete error_callback; 75 | } 76 | 77 | void HandleErrorCallback() 78 | { 79 | HandleScope scope; 80 | 81 | v8::Local argv[] = { 82 | v8::Exception::Error(New(ErrorMessage()).ToLocalChecked())}; 83 | error_callback->Call(1, argv); 84 | } 85 | 86 | void HandleOKCallback() 87 | { 88 | drainQueue(); 89 | callback->Call(0, NULL); 90 | } 91 | 92 | void HandleProgressCallback(const char *data, size_t size) 93 | { 94 | drainQueue(); 95 | } 96 | 97 | void close() 98 | { 99 | input_closed = true; 100 | } 101 | 102 | PCQueue fromNode; 103 | 104 | protected: 105 | void writeToNode(const AsyncProgressWorker::ExecutionProgress &progress, Message &msg) 106 | { 107 | toNode.write(msg); 108 | progress.Send(reinterpret_cast(&toNode), sizeof(toNode)); 109 | } 110 | 111 | bool closed() 112 | { 113 | return input_closed; 114 | } 115 | 116 | Callback *progress; 117 | Callback *error_callback; 118 | PCQueue toNode; 119 | bool input_closed; 120 | 121 | private: 122 | void drainQueue() 123 | { 124 | HandleScope scope; 125 | 126 | // drain the queue - since we might only get called once for many writes 127 | std::deque contents; 128 | toNode.readAll(contents); 129 | 130 | for (Message &msg : contents) 131 | { 132 | v8::Local argv[] = { 133 | New(msg.name.c_str()).ToLocalChecked(), 134 | New(msg.data.c_str()).ToLocalChecked()}; 135 | progress->Call(2, argv); 136 | } 137 | } 138 | }; 139 | 140 | StreamingWorker *create_worker(Callback *, Callback *, Callback *, v8::Local &); 141 | 142 | class StreamWorkerWrapper : public Nan::ObjectWrap 143 | { 144 | public: 145 | static NAN_MODULE_INIT(Init) 146 | { 147 | v8::Local tpl = Nan::New(New); 148 | tpl->SetClassName(Nan::New("StreamingWorker").ToLocalChecked()); 149 | tpl->InstanceTemplate()->SetInternalFieldCount(2); 150 | 151 | SetPrototypeMethod(tpl, "sendToAddon", sendToAddon); 152 | SetPrototypeMethod(tpl, "closeInput", closeInput); 153 | 154 | constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked()); 155 | Nan::Set(target, Nan::New("StreamingWorker").ToLocalChecked(), 156 | Nan::GetFunction(tpl).ToLocalChecked()); 157 | } 158 | 159 | private: 160 | explicit StreamWorkerWrapper(StreamingWorker *worker) : _worker(worker) {} 161 | ~StreamWorkerWrapper() {} 162 | 163 | static NAN_METHOD(New) 164 | { 165 | if (info.IsConstructCall()) 166 | { 167 | Callback *data_callback = new Callback(info[0].As()); 168 | Callback *complete_callback = new Callback(info[1].As()); 169 | Callback *error_callback = new Callback(info[2].As()); 170 | v8::Local options = info[3].As(); 171 | 172 | StreamWorkerWrapper *obj = new StreamWorkerWrapper( 173 | create_worker( 174 | data_callback, 175 | complete_callback, 176 | error_callback, options)); 177 | 178 | obj->Wrap(info.This()); 179 | info.GetReturnValue().Set(info.This()); 180 | 181 | // start the worker 182 | AsyncQueueWorker(obj->_worker); 183 | } 184 | else 185 | { 186 | const int argc = 3; 187 | v8::Local argv[argc] = {info[0], info[1], info[2]}; 188 | v8::Local cons = Nan::New(constructor()); 189 | info.GetReturnValue().Set(cons->NewInstance(argc, argv)); 190 | } 191 | } 192 | 193 | static NAN_METHOD(sendToAddon) 194 | { 195 | v8::String::Utf8Value name(info[0]->ToString()); 196 | v8::String::Utf8Value data(info[1]->ToString()); 197 | StreamWorkerWrapper *obj = Nan::ObjectWrap::Unwrap(info.Holder()); 198 | obj->_worker->fromNode.write(Message(*name, *data)); 199 | } 200 | 201 | static NAN_METHOD(closeInput) 202 | { 203 | StreamWorkerWrapper *obj = Nan::ObjectWrap::Unwrap(info.Holder()); 204 | obj->_worker->close(); 205 | } 206 | 207 | static inline Nan::Persistent &constructor() 208 | { 209 | static Nan::Persistent my_constructor; 210 | return my_constructor; 211 | } 212 | 213 | StreamingWorker *_worker; 214 | }; 215 | -------------------------------------------------------------------------------- /streaming/examples/accumulate/acc-emit.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const worker = require("streaming-worker"); 4 | const path = require("path"); 5 | 6 | var addon_path = path.join(__dirname, "build/Release/accumulate"); 7 | const acc = worker(addon_path); 8 | 9 | acc.to.emit("value", 3); 10 | acc.to.emit("value", 16); 11 | acc.to.emit("value", 42); 12 | acc.to.emit("value", -1); 13 | 14 | acc.from.on('sum', function(value){ 15 | console.log("Accumulated Sum: " + value); 16 | }); -------------------------------------------------------------------------------- /streaming/examples/accumulate/accumulate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "streaming-worker.h" 8 | 9 | using namespace std; 10 | 11 | class Accumulate : public StreamingWorker { 12 | public: 13 | Accumulate(Callback *data 14 | , Callback *complete 15 | , Callback *error_callback, 16 | v8::Local & options) : StreamingWorker(data, complete, error_callback){ 17 | 18 | sum = 0; 19 | filter = ""; 20 | if (options->IsObject() ) { 21 | v8::Local filter_ = options->Get(New("filter").ToLocalChecked()); 22 | if ( filter_->IsString() ) { 23 | v8::String::Utf8Value s(filter_); 24 | filter = *s; 25 | } 26 | } 27 | } 28 | ~Accumulate(){} 29 | 30 | bool filter_by_name(string name) { 31 | return ( filter.empty() || name == filter); 32 | } 33 | 34 | void Execute (const AsyncProgressWorker::ExecutionProgress& progress) { 35 | int value ; 36 | do { 37 | Message m = fromNode.read(); 38 | value = std::stoi(m.data); 39 | if ( filter_by_name(m.name) || value <= 0) { 40 | if ( value > 0 ){ 41 | sum += value; 42 | } 43 | else { 44 | Message tosend("sum", std::to_string(sum)); 45 | writeToNode(progress, tosend); 46 | } 47 | } 48 | } while (value > 0); 49 | } 50 | private: 51 | int sum; 52 | string filter; 53 | }; 54 | 55 | StreamingWorker * create_worker(Callback *data 56 | , Callback *complete 57 | , Callback *error_callback, v8::Local & options) { 58 | return new Accumulate(data, complete, error_callback, options); 59 | } 60 | 61 | NODE_MODULE(accumulate, StreamWorkerWrapper::Init) 62 | -------------------------------------------------------------------------------- /streaming/examples/accumulate/accumulate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const worker = require("streaming-worker"); 4 | const path = require("path"); 5 | const streamify = require('stream-array'); 6 | 7 | var addon_path = path.join(__dirname, "build/Release/accumulate"); 8 | const acc = worker(addon_path); 9 | 10 | const input = acc.to.stream("value", 11 | function () { 12 | acc.to.emit('value', -1); 13 | }); 14 | 15 | streamify([1, 2, 3, 4, 5, 6]).pipe(input); 16 | 17 | acc.from.on('sum', function(value){ 18 | console.log("Accumulated Sum: " + value); 19 | }); -------------------------------------------------------------------------------- /streaming/examples/accumulate/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "accumulate", 5 | "sources": [ "accumulate.cpp" ], 6 | "cflags": ["-Wall", "-std=c++11"], 7 | "cflags!": [ '-fno-exceptions' ], 8 | "cflags_cc!": [ '-fno-exceptions' ], 9 | "include_dirs" : [" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "streaming-worker.h" 8 | 9 | using namespace std; 10 | 11 | class EvenOdd : public StreamingWorker { 12 | public: 13 | EvenOdd(Callback *data 14 | , Callback *complete 15 | , Callback *error_callback, 16 | v8::Local & options) : StreamingWorker(data, complete, error_callback){ 17 | 18 | start = 0; 19 | if (options->IsObject() ) { 20 | v8::Local start_ = options->Get(New("start").ToLocalChecked()); 21 | if ( start_->IsNumber() ) { 22 | start = start_->NumberValue(); 23 | } 24 | } 25 | } 26 | ~EvenOdd(){} 27 | 28 | void Execute (const AsyncProgressWorker::ExecutionProgress& progress) { 29 | int max ; 30 | do { 31 | Message m = fromNode.read(); 32 | max = std::stoi(m.data); 33 | for (int i = start; i <= max; ++i) { 34 | string event = (i % 2 == 0 ? "even_event" : "odd_event"); 35 | Message tosend(event, std::to_string(i)); 36 | writeToNode(progress, tosend); 37 | std::this_thread::sleep_for(chrono::milliseconds(100)); 38 | } 39 | } while (max >= 0); 40 | } 41 | private: 42 | int start; 43 | }; 44 | 45 | // Important: You MUST include this function, and you cannot alter 46 | // the signature at all. The base wrapper class calls this 47 | // to build your particular worker. The prototype for this 48 | // function is defined in addon-streams.h 49 | StreamingWorker * create_worker(Callback *data 50 | , Callback *complete 51 | , Callback *error_callback, v8::Local & options) { 52 | return new EvenOdd(data, complete, error_callback, options); 53 | } 54 | 55 | // Don't forget this! You can change the name of your module, 56 | // but the second parameter should always be as shown. 57 | NODE_MODULE(even_odd_worker, StreamWorkerWrapper::Init) 58 | -------------------------------------------------------------------------------- /streaming/examples/even_odd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "even_odd", 3 | "version": "0.0.1", 4 | "private": true, 5 | "gypfile": true, 6 | "scripts": { 7 | "start": "node even_odd.js" 8 | }, 9 | "dependencies": { 10 | "nan": "*", 11 | "through": "^2.3.8", 12 | "streaming-worker" : "file:../../dist" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /streaming/examples/factorization/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "factorization", 5 | "sources": [ "factorization.cpp" ], 6 | "cflags": ["-Wall", "-std=c++11"], 7 | "include_dirs" : [" 2 | #include 3 | #include 4 | #include "streaming-worker.h" 5 | 6 | using namespace std; 7 | 8 | class Factorization : public StreamingWorker { 9 | public: 10 | Factorization(Callback *data, Callback *complete, Callback *error_callback, v8::Local & options) 11 | : StreamingWorker(data, complete, error_callback){ 12 | 13 | N = -1; 14 | if (options->IsObject() ) { 15 | v8::Local n_ = options->Get(New("n").ToLocalChecked()); 16 | if ( n_->IsNumber() ) { 17 | N = n_->NumberValue(); 18 | } 19 | } 20 | 21 | if ( N < 0 ) { 22 | SetErrorMessage("Cannot compute prime factorization of negative numbers (overflowed long long?)!"); 23 | } 24 | } 25 | 26 | void send_factor(const AsyncProgressWorker::ExecutionProgress& progress, long long factor) { 27 | Message tosend("factor", std::to_string(factor)); 28 | writeToNode(progress, tosend); 29 | } 30 | 31 | void Execute (const AsyncProgressWorker::ExecutionProgress& progress) { 32 | long long n = N; 33 | while (n%2 == 0) 34 | { 35 | send_factor(progress, 2); 36 | n = n/2; 37 | } 38 | 39 | for (long long i = 3; i <= n; i = i+2) { 40 | while (n%i == 0) { 41 | send_factor(progress, i); 42 | n = n/i; 43 | } 44 | } 45 | } 46 | private: 47 | long long N; 48 | }; 49 | 50 | // Important: You MUST include this function, and you cannot alter 51 | // the signature at all. The base wrapper class calls this 52 | // to build your particular worker. The prototype for this 53 | // function is defined in addon-streams.h 54 | StreamingWorker * create_worker(Callback *data 55 | , Callback *complete 56 | , Callback *error_callback, v8::Local & options) { 57 | return new Factorization(data, complete, error_callback, options); 58 | } 59 | 60 | // Don't forget this! You can change the name of your module, 61 | // but the second parameter should always be as shown. 62 | NODE_MODULE(factorization, StreamWorkerWrapper::Init) 63 | -------------------------------------------------------------------------------- /streaming/examples/factorization/factorization.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const worker = require("streaming-worker"); 4 | const path = require("path"); 5 | 6 | var addon_path = path.join(__dirname, "build/Release/factorization"); 7 | 8 | const factorizer = worker(addon_path, {n: 9007199254740991}); 9 | 10 | factorizer.from.on('factor', function(factor){ 11 | console.log("Factor: " + factor); 12 | }); 13 | 14 | factorizer.from.on('error', function(e) { 15 | console.log(e); 16 | }); 17 | 18 | -------------------------------------------------------------------------------- /streaming/examples/factorization/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "factorization", 3 | "version": "0.0.1", 4 | "private": true, 5 | "gypfile": true, 6 | "scripts": { 7 | "start": "node factorization.js" 8 | }, 9 | "dependencies": { 10 | "nan": "*", 11 | "through": "^2.3.8", 12 | "streaming-worker" : "file:../../dist" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /streaming/examples/piping/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "piping", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "start": "node piping.js" 6 | }, 7 | "dependencies": { 8 | "nan": "*", 9 | "through": "^2.3.8", 10 | "streaming-worker" : "file:../../dist" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /streaming/examples/piping/piping.js: -------------------------------------------------------------------------------- 1 | const worker = require("streaming-worker"); 2 | const path = require("path"); 3 | 4 | 5 | var acc_path = path.join(__dirname, "../accumulate/build/Release/accumulate"); 6 | var eo_path = path.join(__dirname, "../even_odd/build/Release/even_odd_worker"); 7 | 8 | // TODO: This example doesn't seem to work on OS X - there is a hangup whenever two addons are used... 9 | // Works on Linux and Windows 10 | // Pull requests welcome :) 11 | const eacc = worker(acc_path, {filter:"even_event"}); 12 | const oacc = worker(acc_path, {filter:"odd_event"}); 13 | const eo = worker(eo_path, {start:1}); 14 | 15 | [eacc, oacc].forEach(function(acc) { 16 | const input = acc.to.stream("value", 17 | function () { 18 | acc.to.emit('value', -1); 19 | }); 20 | 21 | eo.from.stream().pipe(input); 22 | }); 23 | 24 | eacc.from.on('sum', function(value){ 25 | console.log("Accumulated Sum of Evens: " + value); 26 | }); 27 | oacc.from.on('sum', function(value){ 28 | console.log("Accumulated Sum of Odds: " + value); 29 | }); 30 | 31 | eo.to.emit("go", 10); 32 | eo.to.emit("go", -1); 33 | eo.close(); 34 | -------------------------------------------------------------------------------- /streaming/examples/sensor_sim/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "sensor_sim", 5 | "sources": [ "sensor_sim.cpp" ], 6 | "cflags": ["-Wall", "-std=c++11"], 7 | "cflags!": [ '-fno-exceptions' ], 8 | "cflags_cc!": [ '-fno-exceptions' ], 9 | "include_dirs" : [" 2 | #include 3 | #include 4 | #include 5 | #include "streaming-worker.h" 6 | #include "json.hpp" //https://github.com/nlohmann/json 7 | 8 | using namespace std; 9 | using json = nlohmann::json; 10 | 11 | 12 | class Sensor : public StreamingWorker { 13 | public: 14 | Sensor(Callback *data, Callback *complete, Callback *error_callback, v8::Local & options) 15 | : StreamingWorker(data, complete, error_callback){ 16 | 17 | name = "default sensor"; 18 | if (options->IsObject() ) { 19 | v8::Local name_ = options->Get(New("name").ToLocalChecked()); 20 | if ( name_->IsString() ) { 21 | v8::String::Utf8Value s(name_); 22 | name = *s; 23 | } 24 | } 25 | } 26 | 27 | void send_sample(const AsyncProgressWorker::ExecutionProgress& progress, double x, double y, double z) { 28 | json sample; 29 | sample["sensor"] = name; 30 | sample["position"]["x"] = x; 31 | sample["position"]["y"] = y; 32 | sample["position"]["z"] = z; 33 | Message tosend("position_sample", sample.dump()); 34 | writeToNode(progress, tosend); 35 | } 36 | 37 | void Execute (const AsyncProgressWorker::ExecutionProgress& progress) { 38 | std::random_device rd; 39 | std::uniform_real_distribution pos_dist(-1.0, 1.0); 40 | while (!closed()) { 41 | send_sample(progress, pos_dist(rd), pos_dist(rd), pos_dist(rd)); 42 | std::this_thread::sleep_for(chrono::milliseconds(50)); 43 | } 44 | } 45 | private: 46 | string name; 47 | }; 48 | 49 | StreamingWorker * create_worker(Callback *data 50 | , Callback *complete 51 | , Callback *error_callback, v8::Local & options) { 52 | return new Sensor(data, complete, error_callback, options); 53 | } 54 | 55 | NODE_MODULE(sensor_sim, StreamWorkerWrapper::Init) 56 | -------------------------------------------------------------------------------- /streaming/examples/sensor_sim/sensor_sim.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const worker = require("streaming-worker"); 4 | const through = require('through'); 5 | const path = require("path"); 6 | const JSONStream = require('JSONStream'); 7 | 8 | const addon_path = path.join(__dirname, "build/Release/sensor_sim"); 9 | const wobbly_sensor = worker(addon_path, {name: "Head Mounted Display"}); 10 | 11 | // Option 1 - Just use the emitter interface 12 | wobbly_sensor.from.on('position_sample', function(sample){ 13 | console.log("----------- Event -----------"); 14 | console.log(JSON.parse(sample)); 15 | console.log("-----------------------------"); 16 | }); 17 | 18 | 19 | // Option 2, we can work with a streaming interface: 20 | const pluck_and_parse = through(function (data) { 21 | // the data coming in is an array, 22 | // Element 0 is the name of the event emitted by the addon (position_sample) 23 | // Element 1 is the data - which in this case is 24 | this.queue(JSON.parse(data[1])); 25 | }); 26 | 27 | // the stream isn't created unless you ask for it... 28 | const out = wobbly_sensor.from.stream(); 29 | out.pipe(pluck_and_parse) // extract data 30 | .pipe(through(function(sample) { // just print the sample object 31 | console.log(">>>>>>>>>>> Stream >>>>>>>>>>"); 32 | console.log(sample); 33 | console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 34 | })) 35 | 36 | // The addon must poll for the close signal (see sensor_sim.cpp) 37 | setTimeout(function(){wobbly_sensor.close()}, 5000); -------------------------------------------------------------------------------- /streaming/readme.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | `streaming-worker` is designed to give you a simple interface for sending and receiving events/messages from a long running asynchronous C++ Node.js Addon. 3 | 4 | **Note** - this library is really only for creating very specific types of Node.js C++ addons, ones that are relatively long running and either need to receive a continuous stream of inputs or will send a stream of outputs to/from your JavaScript code (or both). The addons operate in separate worker threads, and use `nan`'s [AsyncProgressWorker](https://github.com/nodejs/nan/blob/master/doc/asyncworker.md) to facilitate stream-like and event-like interfaces. 5 | 6 | This library is based on a chapter in [Node C++ Integration](http://scottfrees.com/ebooks/nodecpp/) - which covers its implementation in a lot more detail. 7 | 8 | # Usage 9 | `streaming-worker` is a C++/JS SDK for building streaming addons - it's not an addon itself!. You create addons by inheriting from the `StreamingWorker` abstract class defined in the SDK. Your addon, at a minimum, needs to implement a few virtual functions (most importantly, `Execute`), and can utilize standard methods to read and write `Message` objects to and from JavaScript. `Message` objects are name/value (string) pairs. 10 | 11 | Once your C++ addon is built, you'll package it up with the JavaScript adapter (`index.js`) in the SDK. This exports a single factory method which accepts a path to the asynchronous C++ Node.js addon you've built. The second parameter (optional) is for any parameters you wish to send to C++ to initialize the addon. 12 | 13 | ## Step 1: Download `/dist` 14 | To build an addon using this interface, you need to download the C++ header and JavaScript hook/adapter. You can get the source from github: 15 | 16 | ``` 17 | git clone https://github.com/freezer333/nodecpp-demo.git 18 | cd nodecpp-demo/streaming 19 | ``` 20 | 21 | ## Step 2: Build your C++ addon 22 | When you download the code, the `/dist` directory contains all you need to create a new addon. To build an addon, you need to setup (probably in a seperate directory) a `binding.gyp` file, a `package.json` file, and whatever C++ source files you'll need for your addon. 23 | 24 | ### Setup binding.gyp 25 | Your `binding.gyp` file is the input to the build process, it's necessary whenever you are creating C++ addons. To use the SDK, you need to make sure you (at a minimum) add the necessary information so `node-gyp` can find the `/dist` directory with the SDK's header file and so it can use `NAN`. In addition, you'll probably want to enable C++11 at least. 26 | 27 | ```js 28 | { 29 | "targets": [ 30 | { 31 | "target_name": "ADDON_NAME", 32 | "sources": [ "YOUR ADDON SOURCE.cpp" ], 33 | "cflags": ["-Wall", "-std=c++11"], 34 | "include_dirs" : [" & options) 82 | : StreamingWorker(data, complete, error_callback) // MUST pass the callbacks to the base constructor! 83 | { 84 | if (options->IsObject() ) { 85 | // extract any options you want to work with. The user of the addon must supply these from JavaScript 86 | } 87 | } 88 | ... 89 | }; 90 | ``` 91 | 92 | #### Addon `Execute` method 93 | Once the addon is created, the base class will queue your addon up as a worker thread, hooked into `libuv`'s event loop. Your addon will be invoked within its `Execute` function. **Beware, this function is executed in a worker thread, not the Node.js event loop thread!**. Synchronization when accessing any state variables (member variables) is your responsibility. The `Execute` function must accept a `progress` object, which is defined by `nan`'s [AsyncProgressWorker](https://github.com/nodejs/nan/blob/master/doc/asyncworker.md) class. You won't use it directly, but you'll pass it along to the sending method when you want to send messages to Node.js. 94 | 95 | ```cpp 96 | // Member method of MyStreamingAddon 97 | void Execute (const AsyncProgressWorker::ExecutionProgress& progress) { 98 | // send and receive messages from Node.js code 99 | } 100 | ``` 101 | 102 | You communicate with Node.js through `Message` objects - which are simple name value pairs (values are strings, feel free to extend the implementation to accomodate other types/templates!). 103 | 104 | 105 | ```cpp 106 | /// defined in streaming-worker.h 107 | class Message { 108 | public: 109 | string name; 110 | string data; 111 | Message(string name, string data) : name(name), data(data){} 112 | }; 113 | ``` 114 | 115 | Your addon inherits a thread-safe queues, `fromNode`, for reading messages sent to it from JavaScript (see below for the JavaScript API). You can extract Message objects from the queue at any time by calling `read()` - but keep in mind this is a *blocking* call. 116 | 117 | Your addon also inherits a thread-safe method for sending messages back to JavaScript, called `writeToNode`. This call requires two parameters, the `progress` object passed into your `Execute` method, and the message to send. 118 | 119 | Below is a simple `Execute` function that receives a message from Node and just returns the square. It stops if it receives -1. 120 | 121 | ```cpp 122 | // Member method of MyStreamingAddon 123 | void Execute (const AsyncProgressWorker::ExecutionProgress& progress) { 124 | int value; 125 | do { 126 | Message m = fromNode.read(); 127 | value = std::stoi(m.data); 128 | Message tosend("square", std::to_string(value*value)); 129 | writeToNode(progress, tosend); 130 | } while (value > 0); 131 | } 132 | ``` 133 | 134 | #### Addon setup code 135 | You need to add just a little bit of boilerplate C++ code to properly bootstrap your addon. The following functions are defined outside of your addon class. 136 | 137 | The first is a factory method named `create_worker`. You MUST include this function, and you cannot alter the signature at all. The base wrapper class calls this to build your particular worker. The prototype for this function is defined in addon-streams.h 138 | 139 | ```cpp 140 | StreamingWorker * create_worker(Callback *data 141 | , Callback *complete 142 | , Callback *error_callback, v8::Local & options) { 143 | 144 | return new MyStreamingAddon(data, complete, error_callback, options); 145 | } 146 | ``` 147 | Lastly, you need to initialize your module using the standard Node.js convention. Replace with whatever you named your module (`target_name` in `binding.gyp`). Keep `StreamWorkerWrapper::Init` unchanged, it's the bootstrap method defined in `streaming-worker.h`. It ultimately calls the `create_worker` method defined above. 148 | 149 | ```cpp 150 | NODE_MODULE(, StreamWorkerWrapper::Init) 151 | ``` 152 | 153 | ## Step 3: Write your JavaScript program 154 | A Node.js program that uses the addon must require the `streaming-worker` module: 155 | 156 | ```js 157 | const worker = require("streaming-worker"); 158 | ``` 159 | Since you've already downloaded the SDK to develop your addon, you might want to just use your local `streaming-worker` module - you can just put the following in your `package.json` file: 160 | 161 | ```js 162 | "dependencies": { 163 | "nan": "*", 164 | "streaming-worker" : "file:" 165 | } 166 | ``` 167 | 168 | Alternatively, you can do an `npm install --save streaming-worker`. The module exports a factory function, which is used to startup your C++ addon. All you need to do is give it the file path where your addon is located: 169 | 170 | ```js 171 | const path = require("path"); 172 | var addon_path = path.join(__dirname, "PATH TO ADDON"); 173 | const streaming_addon = worker(addon_path, {foo: "bar"}); 174 | ``` 175 | The optional second parameter is for initialization variables - see the examples below for usage. 176 | 177 | ### Using the Event Emitter API 178 | The `streaming_addon` object represents your C++ addon, and `streaming-worker` has added two `EventEmitter`-like interfaces on it - `to` and `from`. As you might expect, `to` allows you to emit events *to* your addon - your addon will read the messages using the `fromNode.read()` call. The `from` object lets you listen for events sent *from* your addon - which is done in C++ using the `writeToNode` method. 179 | 180 | ```js 181 | 182 | streaming_addon.to.emit("input", "here's something"); 183 | streaming_addon.to.emit("input", "here's something else"); 184 | 185 | streaming_addon.from.on('event', function(message){ 186 | console.log("Got something from the addon!"); 187 | }); 188 | 189 | streaming_addon.from.on('error', function(e) { 190 | console.error("Something has gone wrong in the addon"); 191 | }); 192 | 193 | streaming_addon.from.on('close', function() { 194 | console.error("The addon has terminated (natural causes)"); 195 | }); 196 | ``` 197 | 198 | When you send an event to your addon, the C++ code will read a `Message` object whose name is set to the event name (first parameter of `emit`) and data is set to the actual message (second parameter of `emit`). Likewise, when sending a `Message` object from C++, its name will be the name of the event that gets emitted. 199 | 200 | ### Using the Streaming API 201 | Ok, so that's not exactly "streaming"... the `streaming-worker` adapter also adds two methods for creating actual streams to and from your C++ code too though. To capture the output of your C++ via a stream, you can use the `stream()` method on the `from` object: 202 | 203 | ```js 204 | const str_out = streaming_addon.from.stream(); 205 | ``` 206 | 207 | It's likely you'll want to pipe this to something, I recommend getting familiar with [`through`](https://github.com/dominictarr/through). 208 | 209 | ```js 210 | str_out.pipe( 211 | through(function(message) { 212 | this.queue(JSON.parse(message)); 213 | })).pipe( 214 | through(function(message) { 215 | console.log(message); 216 | })); 217 | ``` 218 | 219 | To create an input stream, us the `stream` method on the `to` object: 220 | 221 | ```js 222 | const str_in = streaming_addon.to.stream("value", 223 | function () { 224 | str_in.to.emit('value', -1); 225 | }); 226 | ``` 227 | Unlike the output stream, you need to specify a few more things when creating the input stream, since `streaming-worker` needs to know how to turn the streamed data into `Message` objects for your addon to read. The first parameter specifies the name that should be attached to each data put into the stream. The second parameter is a callback that will be invoked when the stream is closed. Under normal circumstances, your C++ code is likely to be looking for some sort of sentinal value - and this is your opportunity to send it. 228 | 229 | ```js 230 | const streamify = require('stream-array'); 231 | 232 | // the addon will get -1 when the array is drained, 233 | // because we specified the end callback above 234 | streamify([1, 2, 3]).pipe(input); 235 | ``` 236 | 237 | If the stream is expected to receive the sentinal anyway, then you can just leave the callback undefined. 238 | 239 | ```js 240 | const str_in = streaming_addon.to.stream("value"); 241 | streamify([1, 2, 3, -1]).pipe(input); // the addon receives the -1 it is looking for... 242 | ``` 243 | 244 | # Examples 245 | Below are five examples designed to give you an overview of some of the ways you can interact with addons written using the streaming-worker API. The first two use the event emitter interface to communicate with the addons. The third and fourth examples show you how to use the stream interface. The fifth example combines a few of the examples by piping addons together. 246 | 247 | To get started: 248 | 249 | ``` 250 | git clone https://github.com/freezer333/nodecpp-demo.git 251 | cd nodecpp-demo/streaming 252 | ``` 253 | ## Prime Factorization 254 | This example uses a C++ addon to compute the prime factorization of larg(ish) numbers. The number to be factored is passed in as options to the addon, which immediately begins emitting prime factors as it finds them. The JavaScript code listens to the event emitter interface exposed by the C++ worker and simply prints the factors to the screen. 255 | 256 | The demo is found in `/streaming/examples/factorization`. Do an `npm install` followed by an `npm start` to run it. 257 | 258 | ## Even/Odd Generator 259 | This example shows you how to send input to your addons via the event emitter interface. The `even_odd` C++ code sits in a loop waiting for integer input (N). Once received, if N is not -1, it emits `even_event` and `odd_event` messages for each number from 0...N. 260 | 261 | This example also accepts optional parameters on startup, in this case which number to start from (instead of 0). 262 | 263 | The example itself emits a few inputs (10, then 5 seconds later, 20) to the addon and closes it by emitting a -1 at the end. 264 | 265 | The demo is found in `/streaming/examples/even_odd`. Do an `npm install` followed by an `npm start` to run it. 266 | 267 | ## Sensor Data (simulation) 268 | This example demonstrates creating a **streaming interface** to capture output from a C++ addon. It's meant to mimic what you might see from a positional tracker, like something reporting the position of a head-mounted display in a VR application. The C++ addon emits sensor data at a regular interval. The sensor data emitted is an *object*, which is **serialized to JSON** strings using the [JSON](https://github.com/nlohmann/json) library for C++. 269 | 270 | The JavaScript instantiates the addon, which immediately starts emitting the sensor data. In the example, to interfaces to capture the output are used. 271 | 272 | The first method is the same event emitter interface from the previous examples. In the second, an output stream is created (using [`emit-stream`](https://github.com/substack/emit-stream) internally). That stream is subsequently piped through some transforms to deserialize the JSON and pull out the data (not the message name) using [`through`]. The result is streaming sensor data printed to the console. 273 | 274 | The demo is found in `/streaming/examples/sensor_sim`. Do an `npm install` followed by an `npm start` to run it. 275 | 276 | ## Accumulation in C++ 277 | This example demonstrates streaming data *into* your addon. The addon is a simple accumulator - it receives events, assuming the event data are integers. The addon sums up all the events it reads from the stream and emits a single "sum" event once the stream has been closed. 278 | 279 | In this example, stream closing is propagated to the addon via a single message whose data is -1. This is achieved by specifying a custom `end` callback when creating the addon's input stream. This callback is invoked when the stream is closed. 280 | 281 | The accumulator C++ addon also allows filtering of messages, allowing it to selectively choose which events it adds to the "sum". This demonstrates the use of initialization options again, and is helpful or the final example (below). 282 | 283 | The demo is found in `/streaming/examples/accumulation`. Do an `npm install` followed by an `npm start` to run it. 284 | 285 | ## Piping addons 286 | This example doesn't create any new C++ addons, it just demonstrates how you can pipe together streaming-worker addons. The example starts out by creating an `even_odd` streaming worker. Next, two `accumulator` addons are created with filters on them so one captures only even events (`even_event`) and another captures odd events (`odd_event`). An input stream is created for both the even and odd accumulator, in both cases input stream closing is mapped to sending an event with `-1` as its data. The output stream from the `even_odd` addon is piped into **both** accumulator inputs streams. 287 | 288 | Finally, the `even_odd` addon is kicked off by emitting `10` to it using its event emitter interface. The result is that both accumulators receive all the events, but sum up only even or odd respectively. 289 | 290 | The demo is found in `/streaming/examples/piping`. Do an `npm install` followed by an `npm start` to run it. **Note:** in order to use this example, you also need to do an `npm install` in the `even_odd` and `accumulator` examples! 291 | 292 | *Note - this example is only working on Windows and Linux, there is a hangup whenever two addons are used in OS X - working on it! Pull requests welcome!* 293 | --------------------------------------------------------------------------------