mirror of
https://github.com/nodejs/node.git
synced 2024-11-30 07:27:22 +01:00
9cb96ac828
No need to announce obvious example code as being example code. Remove unneeded "for example" text as one small way to try to keep the docs more concise.. PR-URL: https://github.com/nodejs/node/pull/18890 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Matheus Marchini <matheus@sthima.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com>
1178 lines
31 KiB
Markdown
1178 lines
31 KiB
Markdown
# C++ Addons
|
|
|
|
<!--introduced_in=v0.10.0-->
|
|
|
|
Node.js Addons are dynamically-linked shared objects, written in 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++ 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 the starting-point for an 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(NODE_GYP_MODULE_NAME, 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(NODE_GYP_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 the module
|
|
using a JSON-like format. This file is used by [node-gyp][] -- a tool written
|
|
specifically to compile Node.js Addons.
|
|
|
|
```json
|
|
{
|
|
"targets": [
|
|
{
|
|
"target_name": "addon",
|
|
"sources": [ "hello.cc" ]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
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());
|
|
// Prints: '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.
|
|
|
|
|
|
## N-API
|
|
|
|
> Stability: 1 - Experimental
|
|
|
|
N-API is an API for building native Addons. It is independent from
|
|
the underlying JavaScript runtime (e.g. V8) and is maintained as part of
|
|
Node.js itself. This API will be Application Binary Interface (ABI) stable
|
|
across version of Node.js. It is intended to insulate Addons from
|
|
changes in the underlying JavaScript engine and allow modules
|
|
compiled for one version to run on later versions of Node.js without
|
|
recompilation. Addons are built/packaged with the same approach/tools
|
|
outlined in this document (node-gyp, etc.). The only difference is the
|
|
set of APIs that are used by the native code. Instead of using the V8
|
|
or [Native Abstractions for Node.js][] APIs, the functions available
|
|
in the N-API are used.
|
|
|
|
To use N-API in the above "Hello world" example, replace the content of
|
|
`hello.cc` with the following. All other instructions remain the same.
|
|
|
|
```cpp
|
|
// hello.cc using N-API
|
|
#include <node_api.h>
|
|
|
|
namespace demo {
|
|
|
|
napi_value Method(napi_env env, napi_callback_info args) {
|
|
napi_value greeting;
|
|
napi_status status;
|
|
|
|
status = napi_create_string_utf8(env, "hello", 6, &greeting);
|
|
if (status != napi_ok) return nullptr;
|
|
return greeting;
|
|
}
|
|
|
|
napi_value init(napi_env env, napi_value exports) {
|
|
napi_status status;
|
|
napi_value fn;
|
|
|
|
status = napi_create_function(env, nullptr, 0, Method, nullptr, &fn);
|
|
if (status != napi_ok) return nullptr;
|
|
|
|
status = napi_set_named_property(env, exports, "hello", fn);
|
|
if (status != napi_ok) return nullptr;
|
|
return exports;
|
|
}
|
|
|
|
NAPI_MODULE(NODE_GYP_MODULE_NAME, init)
|
|
|
|
} // namespace demo
|
|
```
|
|
|
|
The functions available and how to use them are documented in the
|
|
section titled [C/C++ Addons - N-API](n-api.html).
|
|
|
|
## 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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
"sources": ["addon.cc", "myexample.cc"]
|
|
```
|
|
|
|
Once the `binding.gyp` file is ready, the example Addons can be configured and
|
|
built using `node-gyp`:
|
|
|
|
```console
|
|
$ 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(NODE_GYP_MODULE_NAME, 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(NODE_GYP_MODULE_NAME, 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);
|
|
// Prints: '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(NODE_GYP_MODULE_NAME, Init)
|
|
|
|
} // namespace demo
|
|
```
|
|
|
|
To test it in JavaScript:
|
|
|
|
```js
|
|
// test.js
|
|
const addon = require('./build/Release/addon');
|
|
|
|
const obj1 = addon('hello');
|
|
const obj2 = addon('world');
|
|
console.log(obj1.msg, obj2.msg);
|
|
// Prints: '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(NODE_GYP_MODULE_NAME, Init)
|
|
|
|
} // namespace demo
|
|
```
|
|
|
|
To test:
|
|
|
|
```js
|
|
// test.js
|
|
const addon = require('./build/Release/addon');
|
|
|
|
const fn = addon();
|
|
console.log(fn());
|
|
// Prints: '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(NODE_GYP_MODULE_NAME, 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::Context;
|
|
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<Context> context = isolate->GetCurrentContext();
|
|
Local<Function> cons = Local<Function>::New(isolate, constructor);
|
|
Local<Object> result =
|
|
cons->NewInstance(context, argc, argv).ToLocalChecked();
|
|
args.GetReturnValue().Set(result);
|
|
}
|
|
}
|
|
|
|
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`:
|
|
|
|
```json
|
|
{
|
|
"targets": [
|
|
{
|
|
"target_name": "addon",
|
|
"sources": [
|
|
"addon.cc",
|
|
"myobject.cc"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Test it with:
|
|
|
|
```js
|
|
// test.js
|
|
const addon = require('./build/Release/addon');
|
|
|
|
const obj = new addon.MyObject(10);
|
|
console.log(obj.plusOne());
|
|
// Prints: 11
|
|
console.log(obj.plusOne());
|
|
// Prints: 12
|
|
console.log(obj.plusOne());
|
|
// Prints: 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
|
|
const obj = addon.createObject();
|
|
// instead of:
|
|
// const 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(NODE_GYP_MODULE_NAME, 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::Context;
|
|
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);
|
|
Local<Context> context = isolate->GetCurrentContext();
|
|
Local<Object> instance =
|
|
cons->NewInstance(context, argc, argv).ToLocalChecked();
|
|
args.GetReturnValue().Set(instance);
|
|
}
|
|
}
|
|
|
|
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<Context> context = isolate->GetCurrentContext();
|
|
Local<Object> instance =
|
|
cons->NewInstance(context, argc, argv).ToLocalChecked();
|
|
|
|
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`:
|
|
|
|
```json
|
|
{
|
|
"targets": [
|
|
{
|
|
"target_name": "addon",
|
|
"sources": [
|
|
"addon.cc",
|
|
"myobject.cc"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Test it with:
|
|
|
|
```js
|
|
// test.js
|
|
const createObject = require('./build/Release/addon');
|
|
|
|
const obj = createObject(10);
|
|
console.log(obj.plusOne());
|
|
// Prints: 11
|
|
console.log(obj.plusOne());
|
|
// Prints: 12
|
|
console.log(obj.plusOne());
|
|
// Prints: 13
|
|
|
|
const obj2 = createObject(20);
|
|
console.log(obj2.plusOne());
|
|
// Prints: 21
|
|
console.log(obj2.plusOne());
|
|
// Prints: 22
|
|
console.log(obj2.plusOne());
|
|
// Prints: 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(NODE_GYP_MODULE_NAME, 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::Context;
|
|
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<Context> context = isolate->GetCurrentContext();
|
|
Local<Function> cons = Local<Function>::New(isolate, constructor);
|
|
Local<Object> instance =
|
|
cons->NewInstance(context, argc, argv).ToLocalChecked();
|
|
args.GetReturnValue().Set(instance);
|
|
}
|
|
}
|
|
|
|
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<Context> context = isolate->GetCurrentContext();
|
|
Local<Object> instance =
|
|
cons->NewInstance(context, argc, argv).ToLocalChecked();
|
|
|
|
args.GetReturnValue().Set(instance);
|
|
}
|
|
|
|
} // namespace demo
|
|
```
|
|
|
|
Test it with:
|
|
|
|
```js
|
|
// test.js
|
|
const addon = require('./build/Release/addon');
|
|
|
|
const obj1 = addon.createObject(10);
|
|
const obj2 = addon.createObject(20);
|
|
const result = addon.add(obj1, obj2);
|
|
|
|
console.log(result);
|
|
// Prints: 30
|
|
```
|
|
|
|
### AtExit hooks
|
|
|
|
An "AtExit" hook is a function that is invoked after the Node.js event loop
|
|
has ended but 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
|
|
#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(at_exit_cb2, cookie);
|
|
AtExit(at_exit_cb2, cookie);
|
|
AtExit(at_exit_cb1, exports->GetIsolate());
|
|
AtExit(sanity_check);
|
|
}
|
|
|
|
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
|
|
|
|
} // namespace demo
|
|
```
|
|
|
|
Test in JavaScript by running:
|
|
|
|
```js
|
|
// test.js
|
|
require('./build/Release/addon');
|
|
```
|
|
|
|
[Embedder's Guide]: https://github.com/v8/v8/wiki/Embedder's%20Guide
|
|
[Linking to Node.js' own dependencies]: #addons_linking_to_node_js_own_dependencies
|
|
[Native Abstractions for Node.js]: https://github.com/nodejs/nan
|
|
[bindings]: https://github.com/TooTallNate/node-bindings
|
|
[download]: https://github.com/nodejs/node-addon-examples
|
|
[examples]: https://github.com/nodejs/nan/tree/master/examples/
|
|
[installation instructions]: https://github.com/nodejs/node-gyp#installation
|
|
[libuv]: https://github.com/libuv/libuv
|
|
[node-gyp]: https://github.com/nodejs/node-gyp
|
|
[require]: modules.html#modules_require
|
|
[v8-docs]: https://v8docs.nodesource.com/
|