C++ addons#
Addons are dynamically-linked shared objects written in C++. Therequire() function can load addons as ordinary Node.js modules.Addons provide an interface between JavaScript and C/C++ libraries.
There are three options for implementing addons:
- Node-API
nan(Native Abstractions for Node.js)- direct use of internal V8, libuv, and Node.js libraries
Unless there is a need for direct access to functionality which is not
exposed by Node-API, use Node-API.Refer toC/C++ addons with Node-API for more information onNode-API.
When not using Node-API, implementing addons becomes more complex, requiring
knowledge of multiple components and APIs:
V8: the C++ library Node.js uses to provide theJavaScript implementation. It provides the mechanisms for creating objects,calling functions, etc. The V8's API is documented mostly in the
v8.hheader file (deps/v8/include/v8.hin the Node.js sourcetree), and is also availableonline.libuv: The C library that implements the Node.js event loop, its workerthreads and all of the asynchronous behaviors of the platform. It alsoserves as a cross-platform abstraction library, giving easy, POSIX-likeaccess across all major operating systems to many common system tasks, suchas interacting with the file system, sockets, timers, and system events. libuvalso provides a threading abstraction similar to POSIX threads formore sophisticated asynchronous addons that need to move beyond thestandard event loop. Addon authors shouldavoid blocking the event loop with I/O or other time-intensive tasks byoffloading work via libuv to non-blocking system operations, worker threads,or a custom use of libuv threads.
Internal Node.js libraries: Node.js itself exports C++ APIs that addons canuse, the most important of which is the
node::ObjectWrapclass.Other statically linked libraries (including OpenSSL): Theseother libraries are located in the
deps/directory in the Node.js sourcetree. Only the libuv, OpenSSL, V8, and zlib symbols are purposefullyre-exported by Node.js and may be used to various extents by addons. SeeLinking to libraries included with Node.js for additional information.
All of the following examples are available fordownload and maybe used as the starting-point for an addon.
Hello world#
This "Hello world" example is a simple addon, written in C++, that is theequivalent of the following JavaScript code:
module.exports.hello =() =>'world';First, create the filehello.cc:
// hello.cc#include<node.h>namespace demo {using v8::FunctionCallbackInfo;using v8::Isolate;using v8::Local;using v8::NewStringType;using v8::Object;using v8::String;using v8::Value;voidMethod(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate(); args.GetReturnValue().Set(String::NewFromUtf8( isolate,"world", NewStringType::kNormal).ToLocalChecked());}voidInitialize(Local<Object> exports){NODE_SET_METHOD(exports,"hello", Method);}NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)}// namespace demoAll Node.js addons must export an initialization function followingthe pattern:
voidInitialize(Local<Object> exports);NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)There is no semi-colon afterNODE_MODULE as it's not a function (seenode.h).
Themodule_name must match the filename of the final binary (excludingthe.node suffix).
In thehello.cc example, then, the initialization function isInitializeand the addon module name isaddon.
When building addons withnode-gyp, using the macroNODE_GYP_MODULE_NAME asthe first parameter ofNODE_MODULE() will ensure that the name of the finalbinary will be passed toNODE_MODULE().
Addons defined withNODE_MODULE() can not be loaded in multiple contexts ormultiple threads at the same time.
Context-aware addons#
There are environments in which Node.js addons may need to be loaded multipletimes in multiple contexts. For example, theElectron runtime runs multipleinstances of Node.js in a single process. Each instance will have its ownrequire() cache, and thus each instance will need a native addon to behavecorrectly when loaded viarequire(). This means that the addonmust support multiple initializations.
A context-aware addon can be constructed by using the macroNODE_MODULE_INITIALIZER, which expands to the name of a function which Node.jswill expect to find when it loads an addon. An addon can thus be initialized asin the following example:
usingnamespace v8;extern"C"NODE_MODULE_EXPORTvoidNODE_MODULE_INITIALIZER(Local<Object> exports, Local<Value>module, Local<Context> context){/* Perform addon initialization steps here. */}Another option is to use the macroNODE_MODULE_INIT(), which will alsoconstruct a context-aware addon. UnlikeNODE_MODULE(), which is used toconstruct an addon around a given addon initializer function,NODE_MODULE_INIT() serves as the declaration of such an initializer to befollowed by a function body.
The following three variables may be used inside the function body following aninvocation ofNODE_MODULE_INIT():
Local<Object> exports,Local<Value> module, andLocal<Context> context
Building a context-aware addon requires careful management of global static datato ensure stability and correctness. Since the addon may be loaded multipletimes, potentially even from different threads, any global static data storedin the addon must be properly protected, and must not contain any persistentreferences to JavaScript objects. The reason for this is that JavaScriptobjects are only valid in one context, and will likely cause a crash whenaccessed from the wrong context or from a different thread than the one on whichthey were created.
The context-aware addon can be structured to avoid global static data byperforming the following steps:
- Define a class which will hold per-addon-instance data and which has a staticmember of the form
staticvoidDeleteInstance(void* data){// Cast `data` to an instance of the class and delete it.} - Heap-allocate an instance of this class in the addon initializer. This can beaccomplished using the
newkeyword. - Call
node::AddEnvironmentCleanupHook(), passing it the above-createdinstance and a pointer toDeleteInstance(). This will ensure the instance isdeleted when the environment is torn down. - Store the instance of the class in a
v8::External, and - Pass the
v8::Externalto all methods exposed to JavaScript by passing ittov8::FunctionTemplate::New()orv8::Function::New()which creates thenative-backed JavaScript functions. The third parameter ofv8::FunctionTemplate::New()orv8::Function::New()accepts thev8::Externaland makes it available in the native callback using thev8::FunctionCallbackInfo::Data()method.
This will ensure that the per-addon-instance data reaches each binding that canbe called from JavaScript. The per-addon-instance data must also be passed intoany asynchronous callbacks the addon may create.
The following example illustrates the implementation of a context-aware addon:
#include<node.h>usingnamespace v8;classAddonData {public:explicitAddonData(Isolate* isolate): call_count(0) {// Ensure this per-addon-instance data is deleted at environment cleanup. node::AddEnvironmentCleanupHook(isolate, DeleteInstance,this); }// Per-addon data.int call_count;staticvoidDeleteInstance(void* data){deletestatic_cast<AddonData*>(data); }};staticvoidMethod(const v8::FunctionCallbackInfo<v8::Value>& info){// Retrieve the per-addon-instance data. AddonData* data =reinterpret_cast<AddonData*>(info.Data().As<External>()->Value()); data->call_count++; info.GetReturnValue().Set((double)data->call_count);}// Initialize this addon to be context-aware.NODE_MODULE_INIT(/* exports, module, context */) { Isolate* isolate = Isolate::GetCurrent();// Create a new instance of `AddonData` for this instance of the addon and// tie its life cycle to that of the Node.js environment. AddonData* data =newAddonData(isolate);// Wrap the data in a `v8::External` so we can pass it to the method we// expose. Local<External> external = External::New(isolate, data);// Expose the method `Method` to JavaScript, and make sure it receives the// per-addon-instance data we created above by passing `external` as the// third parameter to the `FunctionTemplate` constructor. exports->Set(context, String::NewFromUtf8(isolate,"method").ToLocalChecked(), FunctionTemplate::New(isolate, Method, external) ->GetFunction(context).ToLocalChecked()).FromJust();}Worker support#
History
| Version | Changes |
|---|---|
| v14.8.0, v12.19.0 | Cleanup hooks may now be asynchronous. |
In order to be loaded from multiple Node.js environments,such as a main thread and a Worker thread, an add-on needs to either:
- Be an Node-API addon, or
- Be declared as context-aware using
NODE_MODULE_INIT()as described above
In order to supportWorker threads, addons need to clean up any resourcesthey may have allocated when such a thread exits. This can be achieved throughthe usage of theAddEnvironmentCleanupHook() function:
voidAddEnvironmentCleanupHook(v8::Isolate* isolate,void (*fun)(void* arg),void* arg);This function adds a hook that will run before a given Node.js instance shutsdown. If necessary, such hooks can be removed before they are run usingRemoveEnvironmentCleanupHook(), which has the same signature. Callbacks arerun in last-in first-out order.
If necessary, there is an additional pair ofAddEnvironmentCleanupHook()andRemoveEnvironmentCleanupHook() overloads, where the cleanup hook takes acallback function. This can be used for shutting down asynchronous resources,such as any libuv handles registered by the addon.
The followingaddon.cc usesAddEnvironmentCleanupHook:
// addon.cc#include<node.h>#include<assert.h>#include<stdlib.h>using node::AddEnvironmentCleanupHook;using v8::HandleScope;using v8::Isolate;using v8::Local;using v8::Object;// Note: In a real-world application, do not rely on static/global data.staticchar cookie[] ="yum yum";staticint cleanup_cb1_called =0;staticint cleanup_cb2_called =0;staticvoidcleanup_cb1(void* arg){ Isolate* isolate =static_cast<Isolate*>(arg);HandleScopescope(isolate); Local<Object> obj = Object::New(isolate);assert(!obj.IsEmpty());// assert VM is still aliveassert(obj->IsObject()); cleanup_cb1_called++;}staticvoidcleanup_cb2(void* arg){assert(arg ==static_cast<void*>(cookie)); cleanup_cb2_called++;}staticvoidsanity_check(void*){assert(cleanup_cb1_called ==1);assert(cleanup_cb2_called ==1);}// Initialize this addon to be context-aware.NODE_MODULE_INIT(/* exports, module, context */) { Isolate* isolate = Isolate::GetCurrent();AddEnvironmentCleanupHook(isolate, sanity_check,nullptr);AddEnvironmentCleanupHook(isolate, cleanup_cb2, cookie);AddEnvironmentCleanupHook(isolate, cleanup_cb1, isolate);}Test in JavaScript by running:
// test.jsrequire('./build/Release/addon');Building#
Once the source code has been written, it must be compiled into the binaryaddon.node file. To do so, create a file calledbinding.gyp in thetop-level of the project describing the build configuration of the moduleusing a JSON-like format. This file is used bynode-gyp, a tool writtenspecifically to compile Node.js addons.
{"targets":[{"target_name":"addon","sources":["hello.cc"]}]}A version of thenode-gyp utility is bundled and distributed withNode.js as part ofnpm. This version is not made directly available fordevelopers to use and is intended only to support the ability to use thenpm install command to compile and install addons. Developers who wish tousenode-gyp directly can install it using the commandnpm install -g node-gyp. See thenode-gypinstallation instructions formore information, including platform-specific requirements.
Once thebinding.gyp file has been created, usenode-gyp configure togenerate the appropriate project build files for the current platform. Thiswill generate either aMakefile (on Unix platforms) or avcxproj file(on Windows) in thebuild/ directory.
Next, invoke thenode-gyp build command to generate the compiledaddon.nodefile. This will be put into thebuild/Release/ directory.
When usingnpm install to install a Node.js addon, npm uses its own bundledversion ofnode-gyp to perform this same set of actions, generating acompiled version of the addon for the user's platform on demand.
Once built, the binary addon can be used from within Node.js by pointingrequire() to the builtaddon.node module:
// hello.jsconst addon =require('./build/Release/addon');console.log(addon.hello());// Prints: 'world'Because the exact path to the compiled addon binary can vary depending on howit is compiled (i.e. sometimes it may be in./build/Debug/), addons can usethebindings package to load the compiled module.
While thebindings package implementation is more sophisticated in how itlocates addon modules, it is essentially using atry…catch pattern similar to:
try {returnrequire('./build/Release/addon.node');}catch (err) {returnrequire('./build/Debug/addon.node');}Linking to libraries included with Node.js#
Node.js uses statically linked libraries such as V8, libuv, and OpenSSL. Alladdons are required to link to V8 and may link to any of the other dependenciesas well. Typically, this is as simple as including the appropriate#include <...> statements (e.g.#include <v8.h>) andnode-gyp will locatethe appropriate headers automatically. However, there are a few caveats to beaware of:
When
node-gypruns, it will detect the specific release version of Node.jsand download either the full source tarball or just the headers. If the fullsource is downloaded, addons will have complete access to the full set ofNode.js dependencies. However, if only the Node.js headers are downloaded,then only the symbols exported by Node.js will be available.node-gypcan be run using the--nodedirflag pointing at a local Node.jssource image. Using this option, the addon will have access to the full set ofdependencies.
Loading addons usingrequire()#
The filename extension of the compiled addon binary is.node (as opposedto.dll or.so). Therequire() function is written to look forfiles with the.node file extension and initialize those as dynamically-linkedlibraries.
When callingrequire(), the.node extension can usually beomitted 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 orJavaScript files that happen to share the same base name. For instance, ifthere is a fileaddon.js in the same directory as the binaryaddon.node,thenrequire('addon') will give precedence to theaddon.js fileand load it instead.
Native abstractions for Node.js#
Each of the examples illustrated in this document directly use theNode.js and V8 APIs for implementing addons. The V8 API can, and has, changeddramatically from one V8 release to the next (and one major Node.js release tothe next). With each change, addons may need to be updated and recompiled inorder to continue functioning. The Node.js release schedule is designed tominimize the frequency and impact of such changes but there is little thatNode.js can do to ensure stability of the V8 APIs.
TheNative Abstractions for Node.js (ornan) provide a set of tools thataddon developers are recommended to use to keep compatibility between past andfuture releases of V8 and Node.js. See thenanexamples for anillustration of how it can be used.
Node-API#
Node-API is an API for building native addons. It is independent fromthe underlying JavaScript runtime (e.g. V8) and is maintained as part ofNode.js itself. This API will be Application Binary Interface (ABI) stableacross versions of Node.js. It is intended to insulate addons fromchanges in the underlying JavaScript engine and allow modulescompiled for one version to run on later versions of Node.js withoutrecompilation. Addons are built/packaged with the same approach/toolsoutlined in this document (node-gyp, etc.). The only difference is theset of APIs that are used by the native code. Instead of using the V8orNative Abstractions for Node.js APIs, the functions availablein the Node-API are used.
Creating and maintaining an addon that benefits from the ABI stabilityprovided by Node-API carries with it certainimplementation considerations.
To use Node-API in the above "Hello world" example, replace the content ofhello.cc with the following. All other instructions remain the same.
// hello.cc using Node-API#include<node_api.h>namespace demo {napi_valueMethod(napi_env env, napi_callback_info args){ napi_value greeting; napi_status status; status =napi_create_string_utf8(env,"world", NAPI_AUTO_LENGTH, &greeting);if (status != napi_ok)returnnullptr;return greeting;}napi_valueinit(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)returnnullptr; status =napi_set_named_property(env, exports,"hello", fn);if (status != napi_ok)returnnullptr;return exports;}NAPI_MODULE(NODE_GYP_MODULE_NAME, init)}// namespace demoThe functions available and how to use them are documented inC/C++ addons with Node-API.
Addon examples#
Following are some example addons intended to help developers get started. Theexamples use the V8 APIs. Refer to the onlineV8 referencefor help with the various V8 calls, and V8'sEmbedder's Guide for anexplanation of several concepts used such as handles, scopes, functiontemplates, etc.
Each of these examples using the followingbinding.gyp file:
{"targets":[{"target_name":"addon","sources":["addon.cc"]}]}In cases where there is more than one.cc file, simply add the additionalfilename to thesources array:
"sources":["addon.cc","myexample.cc"]Once thebinding.gyp file is ready, the example addons can be configured andbuilt usingnode-gyp:
node-gyp configure buildFunction arguments#
Addons will typically expose objects and functions that can be accessed fromJavaScript 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 fromJavaScript and how to return a result:
// 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 structvoidAdd(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").ToLocalChecked()));return; }// Check the argument typesif (!args[0]->IsNumber() || !args[1]->IsNumber()) { isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate,"Wrong arguments").ToLocalChecked()));return; }// Perform the operationdouble value = args[0].As<Number>()->Value() + args[1].As<Number>()->Value(); Local<Number> num = Number::New(isolate, value);// Set the return value (using the passed in// FunctionCallbackInfo<Value>&) args.GetReturnValue().Set(num);}voidInit(Local<Object> exports){NODE_SET_METHOD(exports,"add", Add);}NODE_MODULE(NODE_GYP_MODULE_NAME, Init)}// namespace demoOnce compiled, the example addon can be required and used from within Node.js:
// test.jsconst 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 howto invoke such callbacks:
// addon.cc#include<node.h>namespace demo {using v8::Context;using v8::Function;using v8::FunctionCallbackInfo;using v8::Isolate;using v8::Local;using v8::Null;using v8::Object;using v8::String;using v8::Value;voidRunCallback(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext(); Local<Function> cb = Local<Function>::Cast(args[0]);constunsigned argc =1; Local<Value> argv[argc] = { String::NewFromUtf8(isolate,"hello world").ToLocalChecked() }; cb->Call(context,Null(isolate), argc, argv).ToLocalChecked();}voidInit(Local<Object> exports, Local<Object>module){NODE_SET_METHOD(module,"exports", RunCallback);}NODE_MODULE(NODE_GYP_MODULE_NAME, Init)}// namespace demoThis example uses a two-argument form ofInit() that receives the fullmodule object as the second argument. This allows the addon to completelyoverwriteexports with a single function instead of adding the function as aproperty ofexports.
To test it, run the following #"#object-factory">#
Addons can create and return new objects from within a C++ function asillustrated in the following example. An object is created and returned with apropertymsg that echoes the string passed tocreateObject():
// addon.cc#include<node.h>namespace demo {using v8::Context;using v8::FunctionCallbackInfo;using v8::Isolate;using v8::Local;using v8::Object;using v8::String;using v8::Value;voidCreateObject(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext(); Local<Object> obj = Object::New(isolate); obj->Set(context, String::NewFromUtf8(isolate,"msg").ToLocalChecked(), args[0]->ToString(context).ToLocalChecked()) .FromJust(); args.GetReturnValue().Set(obj);}voidInit(Local<Object> exports, Local<Object>module){NODE_SET_METHOD(module,"exports", CreateObject);}NODE_MODULE(NODE_GYP_MODULE_NAME, Init)}// namespace demoTo test it in #"#function-factory">#
Another common scenario is creating JavaScript functions that wrap C++functions and returning those back to #"hello world").ToLocalChecked());}voidCreateFunction(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext(); Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction); Local<Function> fn = tpl->GetFunction(context).ToLocalChecked();// omit this to make it anonymous fn->SetName(String::NewFromUtf8( isolate,"theFunction").ToLocalChecked()); args.GetReturnValue().Set(fn);}voidInit(Local<Object> exports, Local<Object>module){NODE_SET_METHOD(module,"exports", CreateFunction);}NODE_MODULE(NODE_GYP_MODULE_NAME, Init)}// namespace demo
To test:
// test.jsconst 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 newinstances to be created using the JavaScriptnew operator:
// addon.cc#include<node.h>#include"myobject.h"namespace demo {using v8::Local;using v8::Object;voidInitAll(Local<Object> exports){ MyObject::Init(exports);}NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)}// namespace demoThen, inmyobject.h, the wrapper class inherits fromnode::ObjectWrap:
// myobject.h#ifndef MYOBJECT_H#define MYOBJECT_H#include<node.h>#include<node_object_wrap.h>namespace demo {classMyObject :public node::ObjectWrap {public:staticvoidInit(v8::Local<v8::Object> exports);private:explicitMyObject(double value =0); ~MyObject();staticvoidNew(const v8::FunctionCallbackInfo<v8::Value>& args);staticvoidPlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);double value_;};}// namespace demo#endifInmyobject.cc, implement the various methods that are to be exposed.In the following code, the methodplusOne() is exposed by adding it to theconstructor's prototype:
// 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::ObjectTemplate;using v8::String;using v8::Value;MyObject::MyObject(double value) :value_(value) {}MyObject::~MyObject() {}voidMyObject::Init(Local<Object> exports){ Isolate* isolate = Isolate::GetCurrent(); Local<Context> context = isolate->GetCurrentContext(); Local<ObjectTemplate> addon_data_tpl = ObjectTemplate::New(isolate); addon_data_tpl->SetInternalFieldCount(1);// 1 field for the MyObject::New() Local<Object> addon_data = addon_data_tpl->NewInstance(context).ToLocalChecked();// Prepare constructor template Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New, addon_data); tpl->SetClassName(String::NewFromUtf8(isolate,"MyObject").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1);// PrototypeNODE_SET_PROTOTYPE_METHOD(tpl,"plusOne", PlusOne); Local<Function> constructor = tpl->GetFunction(context).ToLocalChecked(); addon_data->SetInternalField(0, constructor); exports->Set(context, String::NewFromUtf8( isolate,"MyObject").ToLocalChecked(), constructor).FromJust();}voidMyObject::New(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext();if (args.IsConstructCall()) {// Invoked as constructor: `new MyObject(...)`double value = args[0]->IsUndefined() ?0 : args[0]->NumberValue(context).FromMaybe(0); MyObject* obj =newMyObject(value); obj->Wrap(args.This()); args.GetReturnValue().Set(args.This()); }else {// Invoked as plain function `MyObject(...)`, turn into construct call.constint argc =1; Local<Value> argv[argc] = { args[0] }; Local<Function> cons = args.Data().As<Object>()->GetInternalField(0) .As<Value>().As<Function>(); Local<Object> result = cons->NewInstance(context, argc, argv).ToLocalChecked(); args.GetReturnValue().Set(result); }}voidMyObject::PlusOne(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate(); MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.This()); obj->value_ +=1; args.GetReturnValue().Set(Number::New(isolate, obj->value_));}}// namespace demoTo build this example, themyobject.cc file must be added to thebinding.gyp:
{"targets":[{"target_name":"addon","sources":["addon.cc","myobject.cc"]}]}Test it with:
// test.jsconst addon =require('./build/Release/addon');const obj =new addon.MyObject(10);console.log(obj.plusOne());// Prints: 11console.log(obj.plusOne());// Prints: 12console.log(obj.plusOne());// Prints: 13The destructor for a wrapper object will run when the object isgarbage-collected. For destructor testing, there are command-line flags thatcan be used to make it possible to force garbage collection. These flags areprovided by the underlying V8 JavaScript engine. They are subject to changeor removal at any time. They are not documented by Node.js or V8, and theyshould never be used outside of testing.
During shutdown of the process or worker threads destructors are not calledby the JS engine. Therefore it's the responsibility of the user to trackthese objects and ensure proper destruction to avoid resource leaks.
Factory of wrapped objects#
Alternatively, it is possible to use a factory pattern to avoid explicitlycreating object instances using the JavaScriptnew operator:
const obj = addon.createObject();// instead of:// const obj = new addon.Object();First, thecreateObject() method is implemented inaddon.cc:
// 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;voidCreateObject(const FunctionCallbackInfo<Value>& args){ MyObject::NewInstance(args);}voidInitAll(Local<Object> exports, Local<Object>module){ MyObject::Init();NODE_SET_METHOD(module,"exports", CreateObject);}NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)}// namespace demoInmyobject.h, the static methodNewInstance() is added to handleinstantiating the object. This method takes the place of usingnew in#"myobject.h"namespace demo {using node::AddEnvironmentCleanupHook;using v8::Context;using v8::Function;using v8::FunctionCallbackInfo;using v8::FunctionTemplate;using v8::Global;using v8::Isolate;using v8::Local;using v8::Number;using v8::Object;using v8::String;using v8::Value;// Warning! This is not thread-safe, this addon cannot be used for worker// threads.Global<Function> MyObject::constructor;MyObject::MyObject(double value) :value_(value) {}MyObject::~MyObject() {}voidMyObject::Init(){ Isolate* isolate = Isolate::GetCurrent();// Prepare constructor template Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New); tpl->SetClassName(String::NewFromUtf8(isolate,"MyObject").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1);// PrototypeNODE_SET_PROTOTYPE_METHOD(tpl,"plusOne", PlusOne); Local<Context> context = isolate->GetCurrentContext(); constructor.Reset(isolate, tpl->GetFunction(context).ToLocalChecked());AddEnvironmentCleanupHook(isolate, [](void*) { constructor.Reset(); },nullptr);}voidMyObject::New(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext();if (args.IsConstructCall()) {// Invoked as constructor: `new MyObject(...)`double value = args[0]->IsUndefined() ?0 : args[0]->NumberValue(context).FromMaybe(0); MyObject* obj =newMyObject(value); obj->Wrap(args.This()); args.GetReturnValue().Set(args.This()); }else {// Invoked as plain function `MyObject(...)`, turn into construct call.constint argc =1; Local<Value> argv[argc] = { args[0] }; Local<Function> cons = Local<Function>::New(isolate, constructor); Local<Object> instance = cons->NewInstance(context, argc, argv).ToLocalChecked(); args.GetReturnValue().Set(instance); }}voidMyObject::NewInstance(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate();constunsigned 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);}voidMyObject::PlusOne(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate(); MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.This()); obj->value_ +=1; args.GetReturnValue().Set(Number::New(isolate, obj->value_));}}// namespace demo
Once again, to build this example, themyobject.cc file must be added to thebinding.gyp:
{"targets":[{"target_name":"addon","sources":["addon.cc","myobject.cc"]}]}Test it with:
// test.jsconst createObject =require('./build/Release/addon');const obj =createObject(10);console.log(obj.plusOne());// Prints: 11console.log(obj.plusOne());// Prints: 12console.log(obj.plusOne());// Prints: 13const obj2 =createObject(20);console.log(obj2.plusOne());// Prints: 21console.log(obj2.plusOne());// Prints: 22console.log(obj2.plusOne());// Prints: 23Passing wrapped objects around#
In addition to wrapping and returning C++ objects, it is possible to passwrapped objects around by unwrapping them with the Node.js helper functionnode::ObjectWrap::Unwrap. The following examples shows a functionadd()that can take twoMyObject objects as input arguments:
// addon.cc#include<node.h>#include<node_object_wrap.h>#include"myobject.h"namespace demo {using v8::Context;using v8::FunctionCallbackInfo;using v8::Isolate;using v8::Local;using v8::Number;using v8::Object;using v8::String;using v8::Value;voidCreateObject(const FunctionCallbackInfo<Value>& args){ MyObject::NewInstance(args);}voidAdd(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext(); MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>( args[0]->ToObject(context).ToLocalChecked()); MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>( args[1]->ToObject(context).ToLocalChecked());double sum = obj1->value() + obj2->value(); args.GetReturnValue().Set(Number::New(isolate, sum));}voidInitAll(Local<Object> exports){ MyObject::Init();NODE_SET_METHOD(exports,"createObject", CreateObject);NODE_SET_METHOD(exports,"add", Add);}NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)}// namespace demoInmyobject.h, a new public method is added to allow access to private valuesafter unwrapping the object.
// myobject.h#ifndef MYOBJECT_H#define MYOBJECT_H#include<node.h>#include<node_object_wrap.h>namespace demo {classMyObject :public node::ObjectWrap {public:staticvoidInit();staticvoidNewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);inlinedoublevalue()const{return value_; }private:explicitMyObject(double value =0); ~MyObject();staticvoidNew(const v8::FunctionCallbackInfo<v8::Value>& args);static v8::Global<v8::Function> constructor;double value_;};}// namespace demo#endifThe implementation ofmyobject.cc remains similar to the previous version:
// myobject.cc#include<node.h>#include"myobject.h"namespace demo {using node::AddEnvironmentCleanupHook;using v8::Context;using v8::Function;using v8::FunctionCallbackInfo;using v8::FunctionTemplate;using v8::Global;using v8::Isolate;using v8::Local;using v8::Object;using v8::String;using v8::Value;// Warning! This is not thread-safe, this addon cannot be used for worker// threads.Global<Function> MyObject::constructor;MyObject::MyObject(double value) :value_(value) {}MyObject::~MyObject() {}voidMyObject::Init(){ Isolate* isolate = Isolate::GetCurrent();// Prepare constructor template Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New); tpl->SetClassName(String::NewFromUtf8(isolate,"MyObject").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1); Local<Context> context = isolate->GetCurrentContext(); constructor.Reset(isolate, tpl->GetFunction(context).ToLocalChecked());AddEnvironmentCleanupHook(isolate, [](void*) { constructor.Reset(); },nullptr);}voidMyObject::New(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext();if (args.IsConstructCall()) {// Invoked as constructor: `new MyObject(...)`double value = args[0]->IsUndefined() ?0 : args[0]->NumberValue(context).FromMaybe(0); MyObject* obj =newMyObject(value); obj->Wrap(args.This()); args.GetReturnValue().Set(args.This()); }else {// Invoked as plain function `MyObject(...)`, turn into construct call.constint argc =1; Local<Value> argv[argc] = { args[0] }; Local<Function> cons = Local<Function>::New(isolate, constructor); Local<Object> instance = cons->NewInstance(context, argc, argv).ToLocalChecked(); args.GetReturnValue().Set(instance); }}voidMyObject::NewInstance(const FunctionCallbackInfo<Value>& args){ Isolate* isolate = args.GetIsolate();constunsigned 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 demoTest it with:
// test.jsconst 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