diff options
author | Robert Jefe Lindstaedt <robert.lindstaedt@gmail.com> | 2016-04-21 00:12:40 +0200 |
---|---|---|
committer | James M Snell <jasnell@gmail.com> | 2016-04-20 16:34:27 -0700 |
commit | 0800c0aa7275bf389b157e1568fa61b59285ad86 (patch) | |
tree | a0b739491c4b0170fc333b1b57d61e6d7ae1ca58 /doc/api/addons.md | |
parent | cff2a137f201604cbb3c4e54578b4ebfd20bcaf2 (diff) | |
download | android-node-v8-0800c0aa7275bf389b157e1568fa61b59285ad86.tar.gz android-node-v8-0800c0aa7275bf389b157e1568fa61b59285ad86.tar.bz2 android-node-v8-0800c0aa7275bf389b157e1568fa61b59285ad86.zip |
doc: git mv to .md
* doc: rename .markdown references in content
* doc: rename to .md in tools
* doc: rename to .md in CONTRIBUTING.md
PR-URL: https://github.com/nodejs/node/pull/4747
Reviewed-By: Myles Borins <myles.borins@gmail.com>
Reviewed-By: techjeffharris
Reviewed-By: Johan Bergström <bugs@bergstroem.nu>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Diffstat (limited to 'doc/api/addons.md')
-rw-r--r-- | doc/api/addons.md | 1091 |
1 files changed, 1091 insertions, 0 deletions
diff --git a/doc/api/addons.md b/doc/api/addons.md new file mode 100644 index 0000000000..4668bc546f --- /dev/null +++ b/doc/api/addons.md @@ -0,0 +1,1091 @@ +# Addons + +Node.js Addons are dynamically-linked shared objects, written in C or C++, that +can be loaded into Node.js using the [`require()`][require] function, and used +just as if they were an ordinary Node.js module. They are used primarily to +provide an interface between JavaScript running in Node.js and C/C++ libraries. + +At the moment, the method for implementing Addons is rather complicated, +involving knowledge of several components and APIs : + + - V8: the C++ library Node.js currently uses to provide the + JavaScript implementation. V8 provides the mechanisms for creating objects, + calling functions, etc. V8's API is documented mostly in the + `v8.h` header file (`deps/v8/include/v8.h` in the Node.js source + tree), which is also available [online][v8-docs]. + + - [libuv][]: The C library that implements the Node.js event loop, its worker + threads and all of the asynchronous behaviors of the platform. It also + serves as a cross-platform abstraction library, giving easy, POSIX-like + access across all major operating systems to many common system tasks, such + as interacting with the filesystem, sockets, timers and system events. libuv + also provides a pthreads-like threading abstraction that may be used to + power more sophisticated asynchronous Addons that need to move beyond the + standard event loop. Addon authors are encouraged to think about how to + avoid blocking the event loop with I/O or other time-intensive tasks by + off-loading work via libuv to non-blocking system operations, worker threads + or a custom use of libuv's threads. + + - Internal Node.js libraries. Node.js itself exports a number of C/C++ APIs + that Addons can use — the most important of which is the + `node::ObjectWrap` class. + + - Node.js includes a number of other statically linked libraries including + OpenSSL. These other libraries are located in the `deps/` directory in the + Node.js source tree. Only the V8 and OpenSSL symbols are purposefully + re-exported by Node.js and may be used to various extents by Addons. + See [Linking to Node.js' own dependencies][] for additional information. + +All of the following examples are available for [download][] and may +be used as a starting-point for your own Addon. + +## Hello world + +This "Hello world" example is a simple Addon, written in C++, that is the +equivalent of the following JavaScript code: + +```js +module.exports.hello = () => 'world'; +``` + +First, create the file `hello.cc`: + +```cpp +// hello.cc +#include <node.h> + +namespace demo { + +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +void Method(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world")); +} + +void init(Local<Object> exports) { + NODE_SET_METHOD(exports, "hello", Method); +} + +NODE_MODULE(addon, init) + +} // namespace demo +``` + +Note that all Node.js Addons must export an initialization function following +the pattern: + +```cpp +void Initialize(Local<Object> exports); +NODE_MODULE(module_name, Initialize) +``` + +There is no semi-colon after `NODE_MODULE` as it's not a function (see +`node.h`). + +The `module_name` must match the filename of the final binary (excluding +the .node suffix). + +In the `hello.cc` example, then, the initialization function is `init` and the +Addon module name is `addon`. + +### Building + +Once the source code has been written, it must be compiled into the binary +`addon.node` file. To do so, create a file called `binding.gyp` in the +top-level of the project describing the build configuration of your module +using a JSON-like format. This file is used by [node-gyp][] -- a tool written +specifically to compile Node.js Addons. + +``` +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "hello.cc" ] + } + ] +} +``` + +*Note: A version of the `node-gyp` utility is bundled and distributed with +Node.js as part of `npm`. This version is not made directly available for +developers to use and is intended only to support the ability to use the +`npm install` command to compile and install Addons. Developers who wish to +use `node-gyp` directly can install it using the command +`npm install -g node-gyp`. See the `node-gyp` [installation instructions][] for +more information, including platform-specific requirements.* + +Once the `binding.gyp` file has been created, use `node-gyp configure` to +generate the appropriate project build files for the current platform. This +will generate either a `Makefile` (on Unix platforms) or a `vcxproj` file +(on Windows) in the `build/` directory. + +Next, invoke the `node-gyp build` command to generate the compiled `addon.node` +file. This will be put into the `build/Release/` directory. + +When using `npm install` to install a Node.js Addon, npm uses its own bundled +version of `node-gyp` to perform this same set of actions, generating a +compiled version of the Addon for the user's platform on demand. + +Once built, the binary Addon can be used from within Node.js by pointing +[`require()`][require] to the built `addon.node` module: + +```js +// hello.js +const addon = require('./build/Release/addon'); + +console.log(addon.hello()); // 'world' +``` + +Please see the examples below for further information or +<https://github.com/arturadib/node-qt> for an example in production. + +Because the exact path to the compiled Addon binary can vary depending on how +it is compiled (i.e. sometimes it may be in `./build/Debug/`), Addons can use +the [bindings][] package to load the compiled module. + +Note that while the `bindings` package implementation is more sophisticated +in how it locates Addon modules, it is essentially using a try-catch pattern +similar to: + +```js +try { + return require('./build/Release/addon.node'); +} catch (err) { + return require('./build/Debug/addon.node'); +} +``` + +### Linking to Node.js' own dependencies + +Node.js uses a number of statically linked libraries such as V8, libuv and +OpenSSL. All Addons are required to link to V8 and may link to any of the +other dependencies as well. Typically, this is as simple as including +the appropriate `#include <...>` statements (e.g. `#include <v8.h>`) and +`node-gyp` will locate the appropriate headers automatically. However, there +are a few caveats to be aware of: + +* When `node-gyp` runs, it will detect the specific release version of Node.js +and download either the full source tarball or just the headers. If the full +source is downloaded, Addons will have complete access to the full set of +Node.js dependencies. However, if only the Node.js headers are downloaded, then +only the symbols exported by Node.js will be available. + +* `node-gyp` can be run using the `--nodedir` flag pointing at a local Node.js +source image. Using this option, the Addon will have access to the full set of +dependencies. + +### Loading Addons using require() + +The filename extension of the compiled Addon binary is `.node` (as opposed +to `.dll` or `.so`). The [`require()`][require] function is written to look for +files with the `.node` file extension and initialize those as dynamically-linked +libraries. + +When calling [`require()`][require], the `.node` extension can usually be +omitted and Node.js will still find and initialize the Addon. One caveat, +however, is that Node.js will first attempt to locate and load modules or +JavaScript files that happen to share the same base name. For instance, if +there is a file `addon.js` in the same directory as the binary `addon.node`, +then [`require('addon')`][require] will give precedence to the `addon.js` file +and load it instead. + +## Native Abstractions for Node.js + +Each of the examples illustrated in this document make direct use of the +Node.js and V8 APIs for implementing Addons. It is important to understand +that the V8 API can, and has, changed dramatically from one V8 release to the +next (and one major Node.js release to the next). With each change, Addons may +need to be updated and recompiled in order to continue functioning. The Node.js +release schedule is designed to minimize the frequency and impact of such +changes but there is little that Node.js can do currently to ensure stability +of the V8 APIs. + +The [Native Abstractions for Node.js][] (or `nan`) provide a set of tools that +Addon developers are recommended to use to keep compatibility between past and +future releases of V8 and Node.js. See the `nan` [examples][] for an +illustration of how it can be used. + +## Addon examples + +Following are some example Addons intended to help developers get started. The +examples make use of the V8 APIs. Refer to the online [V8 reference][v8-docs] +for help with the various V8 calls, and V8's [Embedder's Guide][] for an +explanation of several concepts used such as handles, scopes, function +templates, etc. + +Each of these examples using the following `binding.gyp` file: + +``` +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "addon.cc" ] + } + ] +} +``` + +In cases where there is more than one `.cc` file, simply add the additional +filename to the `sources` array. For example: + +``` +"sources": ["addon.cc", "myexample.cc"] +``` + +Once the `binding.gyp` file is ready, the example Addons can be configured and +built using `node-gyp`: + +``` +$ node-gyp configure build +``` + + +### Function arguments + +Addons will typically expose objects and functions that can be accessed from +JavaScript running within Node.js. When functions are invoked from JavaScript, +the input arguments and return value must be mapped to and from the C/C++ +code. + +The following example illustrates how to read function arguments passed from +JavaScript and how to return a result: + +```cpp +// addon.cc +#include <node.h> + +namespace demo { + +using v8::Exception; +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Number; +using v8::Object; +using v8::String; +using v8::Value; + +// This is the implementation of the "add" method +// Input arguments are passed using the +// const FunctionCallbackInfo<Value>& args struct +void Add(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + + // Check the number of arguments passed. + if (args.Length() < 2) { + // Throw an Error that is passed back to JavaScript + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8(isolate, "Wrong number of arguments"))); + return; + } + + // Check the argument types + if (!args[0]->IsNumber() || !args[1]->IsNumber()) { + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8(isolate, "Wrong arguments"))); + return; + } + + // Perform the operation + double value = args[0]->NumberValue() + args[1]->NumberValue(); + Local<Number> num = Number::New(isolate, value); + + // Set the return value (using the passed in + // FunctionCallbackInfo<Value>&) + args.GetReturnValue().Set(num); +} + +void Init(Local<Object> exports) { + NODE_SET_METHOD(exports, "add", Add); +} + +NODE_MODULE(addon, Init) + +} // namespace demo +``` + +Once compiled, the example Addon can be required and used from within Node.js: + +```js +// test.js +const addon = require('./build/Release/addon'); + +console.log('This should be eight:', addon.add(3, 5)); +``` + + +### Callbacks + +It is common practice within Addons to pass JavaScript functions to a C++ +function and execute them from there. The following example illustrates how +to invoke such callbacks: + +```cpp +// addon.cc +#include <node.h> + +namespace demo { + +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Null; +using v8::Object; +using v8::String; +using v8::Value; + +void RunCallback(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + Local<Function> cb = Local<Function>::Cast(args[0]); + const unsigned argc = 1; + Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "hello world") }; + cb->Call(Null(isolate), argc, argv); +} + +void Init(Local<Object> exports, Local<Object> module) { + NODE_SET_METHOD(module, "exports", RunCallback); +} + +NODE_MODULE(addon, Init) + +} // namespace demo +``` + +Note that this example uses a two-argument form of `Init()` that receives +the full `module` object as the second argument. This allows the Addon +to completely overwrite `exports` with a single function instead of +adding the function as a property of `exports`. + +To test it, run the following JavaScript: + +```js +// test.js +const addon = require('./build/Release/addon'); + +addon((msg) => { + console.log(msg); // 'hello world' +}); +``` + +Note that, in this example, the callback function is invoked synchronously. + +### Object factory + +Addons can create and return new objects from within a C++ function as +illustrated in the following example. An object is created and returned with a +property `msg` that echoes the string passed to `createObject()`: + +```cpp +// addon.cc +#include <node.h> + +namespace demo { + +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +void CreateObject(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + + Local<Object> obj = Object::New(isolate); + obj->Set(String::NewFromUtf8(isolate, "msg"), args[0]->ToString()); + + args.GetReturnValue().Set(obj); +} + +void Init(Local<Object> exports, Local<Object> module) { + NODE_SET_METHOD(module, "exports", CreateObject); +} + +NODE_MODULE(addon, Init) + +} // namespace demo +``` + +To test it in JavaScript: + +```js +// test.js +const addon = require('./build/Release/addon'); + +var obj1 = addon('hello'); +var obj2 = addon('world'); +console.log(obj1.msg + ' ' + obj2.msg); // 'hello world' +``` + + +### Function factory + +Another common scenario is creating JavaScript functions that wrap C++ +functions and returning those back to JavaScript: + +```cpp +// addon.cc +#include <node.h> + +namespace demo { + +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +void MyFunction(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world")); +} + +void CreateFunction(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + + Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction); + Local<Function> fn = tpl->GetFunction(); + + // omit this to make it anonymous + fn->SetName(String::NewFromUtf8(isolate, "theFunction")); + + args.GetReturnValue().Set(fn); +} + +void Init(Local<Object> exports, Local<Object> module) { + NODE_SET_METHOD(module, "exports", CreateFunction); +} + +NODE_MODULE(addon, Init) + +} // namespace demo +``` + +To test: + +```js +// test.js +const addon = require('./build/Release/addon'); + +var fn = addon(); +console.log(fn()); // 'hello world' +``` + + +### Wrapping C++ objects + +It is also possible to wrap C++ objects/classes in a way that allows new +instances to be created using the JavaScript `new` operator: + +```cpp +// addon.cc +#include <node.h> +#include "myobject.h" + +namespace demo { + +using v8::Local; +using v8::Object; + +void InitAll(Local<Object> exports) { + MyObject::Init(exports); +} + +NODE_MODULE(addon, InitAll) + +} // namespace demo +``` + +Then, in `myobject.h`, the wrapper class inherits from `node::ObjectWrap`: + +```cpp +// myobject.h +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include <node.h> +#include <node_object_wrap.h> + +namespace demo { + +class MyObject : public node::ObjectWrap { + public: + static void Init(v8::Local<v8::Object> exports); + + private: + explicit MyObject(double value = 0); + ~MyObject(); + + static void New(const v8::FunctionCallbackInfo<v8::Value>& args); + static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args); + static v8::Persistent<v8::Function> constructor; + double value_; +}; + +} // namespace demo + +#endif +``` + +In `myobject.cc`, implement the various methods that are to be exposed. +Below, the method `plusOne()` is exposed by adding it to the constructor's +prototype: + +```cpp +// myobject.cc +#include "myobject.h" + +namespace demo { + +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Isolate; +using v8::Local; +using v8::Number; +using v8::Object; +using v8::Persistent; +using v8::String; +using v8::Value; + +Persistent<Function> MyObject::constructor; + +MyObject::MyObject(double value) : value_(value) { +} + +MyObject::~MyObject() { +} + +void MyObject::Init(Local<Object> exports) { + Isolate* isolate = exports->GetIsolate(); + + // Prepare constructor template + Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New); + tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject")); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + // Prototype + NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne); + + constructor.Reset(isolate, tpl->GetFunction()); + exports->Set(String::NewFromUtf8(isolate, "MyObject"), + tpl->GetFunction()); +} + +void MyObject::New(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + + if (args.IsConstructCall()) { + // Invoked as constructor: `new MyObject(...)` + double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); + MyObject* obj = new MyObject(value); + obj->Wrap(args.This()); + args.GetReturnValue().Set(args.This()); + } else { + // Invoked as plain function `MyObject(...)`, turn into construct call. + const int argc = 1; + Local<Value> argv[argc] = { args[0] }; + Local<Function> cons = Local<Function>::New(isolate, constructor); + args.GetReturnValue().Set(cons->NewInstance(argc, argv)); + } +} + +void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + + MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder()); + obj->value_ += 1; + + args.GetReturnValue().Set(Number::New(isolate, obj->value_)); +} + +} // namespace demo +``` + +To build this example, the `myobject.cc` file must be added to the +`binding.gyp`: + +``` +{ + "targets": [ + { + "target_name": "addon", + "sources": [ + "addon.cc", + "myobject.cc" + ] + } + ] +} +``` + +Test it with: + +```js +// test.js +const addon = require('./build/Release/addon'); + +var obj = new addon.MyObject(10); +console.log(obj.plusOne()); // 11 +console.log(obj.plusOne()); // 12 +console.log(obj.plusOne()); // 13 +``` + +### Factory of wrapped objects + +Alternatively, it is possible to use a factory pattern to avoid explicitly +creating object instances using the JavaScript `new` operator: + +```js +var obj = addon.createObject(); +// instead of: +// var obj = new addon.Object(); +``` + +First, the `createObject()` method is implemented in `addon.cc`: + +```cpp +// addon.cc +#include <node.h> +#include "myobject.h" + +namespace demo { + +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +void CreateObject(const FunctionCallbackInfo<Value>& args) { + MyObject::NewInstance(args); +} + +void InitAll(Local<Object> exports, Local<Object> module) { + MyObject::Init(exports->GetIsolate()); + + NODE_SET_METHOD(module, "exports", CreateObject); +} + +NODE_MODULE(addon, InitAll) + +} // namespace demo +``` + +In `myobject.h`, the static method `NewInstance()` is added to handle +instantiating the object. This method takes the place of using `new` in +JavaScript: + +```cpp +// myobject.h +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include <node.h> +#include <node_object_wrap.h> + +namespace demo { + +class MyObject : public node::ObjectWrap { + public: + static void Init(v8::Isolate* isolate); + static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args); + + private: + explicit MyObject(double value = 0); + ~MyObject(); + + static void New(const v8::FunctionCallbackInfo<v8::Value>& args); + static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args); + static v8::Persistent<v8::Function> constructor; + double value_; +}; + +} // namespace demo + +#endif +``` + +The implementation in `myobject.cc` is similar to the previous example: + +```cpp +// myobject.cc +#include <node.h> +#include "myobject.h" + +namespace demo { + +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Isolate; +using v8::Local; +using v8::Number; +using v8::Object; +using v8::Persistent; +using v8::String; +using v8::Value; + +Persistent<Function> MyObject::constructor; + +MyObject::MyObject(double value) : value_(value) { +} + +MyObject::~MyObject() { +} + +void MyObject::Init(Isolate* isolate) { + // Prepare constructor template + Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New); + tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject")); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + // Prototype + NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne); + + constructor.Reset(isolate, tpl->GetFunction()); +} + +void MyObject::New(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + + if (args.IsConstructCall()) { + // Invoked as constructor: `new MyObject(...)` + double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); + MyObject* obj = new MyObject(value); + obj->Wrap(args.This()); + args.GetReturnValue().Set(args.This()); + } else { + // Invoked as plain function `MyObject(...)`, turn into construct call. + const int argc = 1; + Local<Value> argv[argc] = { args[0] }; + Local<Function> cons = Local<Function>::New(isolate, constructor); + args.GetReturnValue().Set(cons->NewInstance(argc, argv)); + } +} + +void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + + const unsigned argc = 1; + Local<Value> argv[argc] = { args[0] }; + Local<Function> cons = Local<Function>::New(isolate, constructor); + Local<Object> instance = cons->NewInstance(argc, argv); + + args.GetReturnValue().Set(instance); +} + +void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + + MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder()); + obj->value_ += 1; + + args.GetReturnValue().Set(Number::New(isolate, obj->value_)); +} + +} // namespace demo +``` + +Once again, to build this example, the `myobject.cc` file must be added to the +`binding.gyp`: + +``` +{ + "targets": [ + { + "target_name": "addon", + "sources": [ + "addon.cc", + "myobject.cc" + ] + } + ] +} +``` + +Test it with: + +```js +// test.js +const createObject = require('./build/Release/addon'); + +var obj = createObject(10); +console.log(obj.plusOne()); // 11 +console.log(obj.plusOne()); // 12 +console.log(obj.plusOne()); // 13 + +var obj2 = createObject(20); +console.log(obj2.plusOne()); // 21 +console.log(obj2.plusOne()); // 22 +console.log(obj2.plusOne()); // 23 +``` + + +### Passing wrapped objects around + +In addition to wrapping and returning C++ objects, it is possible to pass +wrapped objects around by unwrapping them with the Node.js helper function +`node::ObjectWrap::Unwrap`. The following examples shows a function `add()` +that can take two `MyObject` objects as input arguments: + +```cpp +// addon.cc +#include <node.h> +#include <node_object_wrap.h> +#include "myobject.h" + +namespace demo { + +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Number; +using v8::Object; +using v8::String; +using v8::Value; + +void CreateObject(const FunctionCallbackInfo<Value>& args) { + MyObject::NewInstance(args); +} + +void Add(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + + MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>( + args[0]->ToObject()); + MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>( + args[1]->ToObject()); + + double sum = obj1->value() + obj2->value(); + args.GetReturnValue().Set(Number::New(isolate, sum)); +} + +void InitAll(Local<Object> exports) { + MyObject::Init(exports->GetIsolate()); + + NODE_SET_METHOD(exports, "createObject", CreateObject); + NODE_SET_METHOD(exports, "add", Add); +} + +NODE_MODULE(addon, InitAll) + +} // namespace demo +``` + +In `myobject.h`, a new public method is added to allow access to private values +after unwrapping the object. + +```cpp +// myobject.h +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include <node.h> +#include <node_object_wrap.h> + +namespace demo { + +class MyObject : public node::ObjectWrap { + public: + static void Init(v8::Isolate* isolate); + static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args); + inline double value() const { return value_; } + + private: + explicit MyObject(double value = 0); + ~MyObject(); + + static void New(const v8::FunctionCallbackInfo<v8::Value>& args); + static v8::Persistent<v8::Function> constructor; + double value_; +}; + +} // namespace demo + +#endif +``` + +The implementation of `myobject.cc` is similar to before: + +```cpp +// myobject.cc +#include <node.h> +#include "myobject.h" + +namespace demo { + +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::Persistent; +using v8::String; +using v8::Value; + +Persistent<Function> MyObject::constructor; + +MyObject::MyObject(double value) : value_(value) { +} + +MyObject::~MyObject() { +} + +void MyObject::Init(Isolate* isolate) { + // Prepare constructor template + Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New); + tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject")); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + constructor.Reset(isolate, tpl->GetFunction()); +} + +void MyObject::New(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + + if (args.IsConstructCall()) { + // Invoked as constructor: `new MyObject(...)` + double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); + MyObject* obj = new MyObject(value); + obj->Wrap(args.This()); + args.GetReturnValue().Set(args.This()); + } else { + // Invoked as plain function `MyObject(...)`, turn into construct call. + const int argc = 1; + Local<Value> argv[argc] = { args[0] }; + Local<Function> cons = Local<Function>::New(isolate, constructor); + args.GetReturnValue().Set(cons->NewInstance(argc, argv)); + } +} + +void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + + const unsigned argc = 1; + Local<Value> argv[argc] = { args[0] }; + Local<Function> cons = Local<Function>::New(isolate, constructor); + Local<Object> instance = cons->NewInstance(argc, argv); + + args.GetReturnValue().Set(instance); +} + +} // namespace demo +``` + +Test it with: + +```js +// test.js +const addon = require('./build/Release/addon'); + +var obj1 = addon.createObject(10); +var obj2 = addon.createObject(20); +var result = addon.add(obj1, obj2); + +console.log(result); // 30 +``` + +### AtExit hooks + +An "AtExit" hook is a function that is invoked after the Node.js event loop +has ended by before the JavaScript VM is terminated and Node.js shuts down. +"AtExit" hooks are registered using the `node::AtExit` API. + +#### void AtExit(callback, args) + +* `callback`: `void (*)(void*)` - A pointer to the function to call at exit. +* `args`: `void*` - A pointer to pass to the callback at exit. + +Registers exit hooks that run after the event loop has ended but before the VM +is killed. + +AtExit takes two parameters: a pointer to a callback function to run at exit, +and a pointer to untyped context data to be passed to that callback. + +Callbacks are run in last-in first-out order. + +The following `addon.cc` implements AtExit: + +```cpp +// addon.cc +#undef NDEBUG +#include <assert.h> +#include <stdlib.h> +#include <node.h> + +namespace demo { + +using node::AtExit; +using v8::HandleScope; +using v8::Isolate; +using v8::Local; +using v8::Object; + +static char cookie[] = "yum yum"; +static int at_exit_cb1_called = 0; +static int at_exit_cb2_called = 0; + +static void at_exit_cb1(void* arg) { + Isolate* isolate = static_cast<Isolate*>(arg); + HandleScope scope(isolate); + Local<Object> obj = Object::New(isolate); + assert(!obj.IsEmpty()); // assert VM is still alive + assert(obj->IsObject()); + at_exit_cb1_called++; +} + +static void at_exit_cb2(void* arg) { + assert(arg == static_cast<void*>(cookie)); + at_exit_cb2_called++; +} + +static void sanity_check(void*) { + assert(at_exit_cb1_called == 1); + assert(at_exit_cb2_called == 2); +} + +void init(Local<Object> exports) { + AtExit(sanity_check); + AtExit(at_exit_cb2, cookie); + AtExit(at_exit_cb2, cookie); + AtExit(at_exit_cb1, exports->GetIsolate()); +} + +NODE_MODULE(addon, init); + +} // namespace demo +``` + +Test in JavaScript by running: + +```js +// test.js +const addon = require('./build/Release/addon'); +``` + +[bindings]: https://github.com/TooTallNate/node-bindings +[download]: https://github.com/nodejs/node-addon-examples +[Embedder's Guide]: https://developers.google.com/v8/embed +[examples]: https://github.com/nodejs/nan/tree/master/examples/ +[installation instructions]: https://github.com/nodejs/node-gyp#installation +[libuv]: https://github.com/libuv/libuv +[Linking to Node.js' own dependencies]: #addons_linking_to_node_js_own_dependencies +[Native Abstractions for Node.js]: https://github.com/nodejs/nan +[node-gyp]: https://github.com/nodejs/node-gyp +[require]: globals.html#globals_require +[v8-docs]: https://v8docs.nodesource.com/ |