From 5af645f64900044971787b092567998be784dcb5 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Fri, 19 Jun 2020 13:12:26 -0700 Subject: [PATCH] src: add Addon class * separate out instance-related APIs from `ObjectWrap` into a new class `InstanceWrap` which then becomes a base class for `ObjectWrap`. * Add `Addon` class as a subclass of `InstanceWrap`, reimplementing `Unwrap()` to retrieve the instance data using `GetInstanceData()` of `Napi::Env` instead of `napi_unwrap()`. * Add macros `NODE_API_ADDON()` and `NODE_API_NAMED_ADDON()` to load an add-on from its `Addon` subclass definition. Bindings created like this perform slightly worse than static ones in exchange for the benefit of having the context of a class instance as their C++ `this` object. This way, they avoid having to call `info.GetInstanceData()` in the bindings, which brings with it the risk that the wrong `ClassName` will end up in the template parameter thus resulting in a hard-to-track-down segfault. Static bindings can still be created and associated with the `exports` object and they can use `Napi::Env::GetInstanceData()` to retrieve the add-on instance. PR-URL: https://github.com/nodejs/node-addon-api/pull/749 Reviewed-By: Michael Dawson --- README.md | 1 + benchmark/function_args.cc | 64 +++ benchmark/function_args.js | 22 +- benchmark/property_descriptor.cc | 31 ++ benchmark/property_descriptor.js | 16 +- doc/addon.md | 518 ++++++++++++++++++ napi-inl.h | 870 +++++++++++++++++-------------- napi.h | 250 +++++---- package.json | 1 + test/addon.cc | 36 ++ test/addon.js | 12 + test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 4 +- 14 files changed, 1312 insertions(+), 516 deletions(-) create mode 100644 doc/addon.md create mode 100644 test/addon.cc create mode 100644 test/addon.js diff --git a/README.md b/README.md index dfc7c953e..1f6e34e75 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ The oldest Node.js version supported by the current version of node-addon-api is The following is the documentation for node-addon-api. + - [Addon Structure](doc/addon.md) - [Basic Types](doc/basic_types.md) - [Array](doc/basic_types.md#array) - [Symbol](doc/symbol.md) diff --git a/benchmark/function_args.cc b/benchmark/function_args.cc index 7dcc7c781..54bdbe342 100644 --- a/benchmark/function_args.cc +++ b/benchmark/function_args.cc @@ -78,6 +78,66 @@ static void FourArgFunction(const Napi::CallbackInfo& info) { Napi::Value argv3 = info[3]; (void) argv3; } +#if NAPI_VERSION > 5 +class FunctionArgsBenchmark : public Napi::Addon { + public: + FunctionArgsBenchmark(Napi::Env env, Napi::Object exports) { + DefineAddon(exports, { + InstanceValue("addon", DefineProperties(Napi::Object::New(env), { + InstanceMethod("noArgFunction", &FunctionArgsBenchmark::NoArgFunction), + InstanceMethod("oneArgFunction", + &FunctionArgsBenchmark::OneArgFunction), + InstanceMethod("twoArgFunction", + &FunctionArgsBenchmark::TwoArgFunction), + InstanceMethod("threeArgFunction", + &FunctionArgsBenchmark::ThreeArgFunction), + InstanceMethod("fourArgFunction", + &FunctionArgsBenchmark::FourArgFunction), + }), napi_enumerable), + InstanceValue("addon_templated", + DefineProperties(Napi::Object::New(env), { + InstanceMethod<&FunctionArgsBenchmark::NoArgFunction>( + "noArgFunction"), + InstanceMethod<&FunctionArgsBenchmark::OneArgFunction>( + "oneArgFunction"), + InstanceMethod<&FunctionArgsBenchmark::TwoArgFunction>( + "twoArgFunction"), + InstanceMethod<&FunctionArgsBenchmark::ThreeArgFunction>( + "threeArgFunction"), + InstanceMethod<&FunctionArgsBenchmark::FourArgFunction>( + "fourArgFunction"), + }), napi_enumerable), + }); + } + private: + void NoArgFunction(const Napi::CallbackInfo& info) { + (void) info; + } + + void OneArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; + } + + void TwoArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; + Napi::Value argv1 = info[1]; (void) argv1; + } + + void ThreeArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; + Napi::Value argv1 = info[1]; (void) argv1; + Napi::Value argv2 = info[2]; (void) argv2; + } + + void FourArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; + Napi::Value argv1 = info[1]; (void) argv1; + Napi::Value argv2 = info[2]; (void) argv2; + Napi::Value argv3 = info[3]; (void) argv3; + } +}; +#endif // NAPI_VERSION > 5 + static Napi::Object Init(Napi::Env env, Napi::Object exports) { napi_value no_arg_function, one_arg_function, two_arg_function, three_arg_function, four_arg_function; @@ -147,6 +207,10 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) { templated["fourArgFunction"] = Napi::Function::New(env); exports["templated"] = templated; +#if NAPI_VERSION > 5 + FunctionArgsBenchmark::Init(env, exports); +#endif // NAPI_VERSION > 5 + return exports; } diff --git a/benchmark/function_args.js b/benchmark/function_args.js index 3dee09a68..e7fb6636f 100644 --- a/benchmark/function_args.js +++ b/benchmark/function_args.js @@ -4,16 +4,22 @@ const addonName = path.basename(__filename, '.js'); [ addonName, addonName + '_noexcept' ] .forEach((addonName) => { - const rootAddon = require(`./build/Release/${addonName}`); + const rootAddon = require('bindings')({ + bindings: addonName, + module_root: __dirname + }); + delete rootAddon.path; const implems = Object.keys(rootAddon); + const maxNameLength = + implems.reduce((soFar, value) => Math.max(soFar, value.length), 0); const anObject = {}; - console.log(`${addonName}: `); + console.log(`\n${addonName}: `); console.log('no arguments:'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].noArgFunction; - return suite.add(implem, () => fn()); + return suite.add(implem.padStart(maxNameLength, ' '), () => fn()); }, new Benchmark.Suite) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -21,7 +27,7 @@ const addonName = path.basename(__filename, '.js'); console.log('one argument:'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].oneArgFunction; - return suite.add(implem, () => fn('x')); + return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x')); }, new Benchmark.Suite) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -29,7 +35,7 @@ const addonName = path.basename(__filename, '.js'); console.log('two arguments:'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].twoArgFunction; - return suite.add(implem, () => fn('x', 12)); + return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x', 12)); }, new Benchmark.Suite) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -37,7 +43,8 @@ const addonName = path.basename(__filename, '.js'); console.log('three arguments:'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].threeArgFunction; - return suite.add(implem, () => fn('x', 12, true)); + return suite.add(implem.padStart(maxNameLength, ' '), + () => fn('x', 12, true)); }, new Benchmark.Suite) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -45,7 +52,8 @@ const addonName = path.basename(__filename, '.js'); console.log('four arguments:'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].fourArgFunction; - return suite.add(implem, () => fn('x', 12, true, anObject)); + return suite.add(implem.padStart(maxNameLength, ' '), + () => fn('x', 12, true, anObject)); }, new Benchmark.Suite) .on('cycle', (event) => console.log(String(event.target))) .run(); diff --git a/benchmark/property_descriptor.cc b/benchmark/property_descriptor.cc index e4e26e7c9..19803f595 100644 --- a/benchmark/property_descriptor.cc +++ b/benchmark/property_descriptor.cc @@ -26,6 +26,33 @@ static void Setter(const Napi::CallbackInfo& info) { (void) info[0]; } +#if NAPI_VERSION > 5 +class PropDescBenchmark : public Napi::Addon { + public: + PropDescBenchmark(Napi::Env, Napi::Object exports) { + DefineAddon(exports, { + InstanceAccessor("addon", + &PropDescBenchmark::Getter, + &PropDescBenchmark::Setter, + napi_enumerable), + InstanceAccessor<&PropDescBenchmark::Getter, + &PropDescBenchmark::Setter>("addon_templated", + napi_enumerable), + }); + } + + private: + Napi::Value Getter(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), 42); + } + + void Setter(const Napi::CallbackInfo& info, const Napi::Value& val) { + (void) info[0]; + (void) val; + } +}; +#endif // NAPI_VERSION > 5 + static Napi::Object Init(Napi::Env env, Napi::Object exports) { napi_status status; napi_property_descriptor core_prop = { @@ -54,6 +81,10 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) { Napi::PropertyDescriptor::Accessor("templated", napi_enumerable)); +#if NAPI_VERSION > 5 + PropDescBenchmark::Init(env, exports); +#endif // NAPI_VERSION > 5 + return exports; } diff --git a/benchmark/property_descriptor.js b/benchmark/property_descriptor.js index cab510601..848aaaf4a 100644 --- a/benchmark/property_descriptor.js +++ b/benchmark/property_descriptor.js @@ -4,17 +4,23 @@ const addonName = path.basename(__filename, '.js'); [ addonName, addonName + '_noexcept' ] .forEach((addonName) => { - const rootAddon = require(`./build/Release/${addonName}`); + const rootAddon = require('bindings')({ + bindings: addonName, + module_root: __dirname + }); + delete rootAddon.path; const getters = new Benchmark.Suite; const setters = new Benchmark.Suite; + const maxNameLength = Object.keys(rootAddon) + .reduce((soFar, value) => Math.max(soFar, value.length), 0); - console.log(`${addonName}: `); + console.log(`\n${addonName}: `); Object.keys(rootAddon).forEach((key) => { - getters.add(`${key} getter`, () => { + getters.add(`${key} getter`.padStart(maxNameLength + 7), () => { const x = rootAddon[key]; }); - setters.add(`${key} setter`, () => { + setters.add(`${key} setter`.padStart(maxNameLength + 7), () => { rootAddon[key] = 5; }) }); @@ -23,6 +29,8 @@ const addonName = path.basename(__filename, '.js'); .on('cycle', (event) => console.log(String(event.target))) .run(); + console.log(''); + setters .on('cycle', (event) => console.log(String(event.target))) .run(); diff --git a/doc/addon.md b/doc/addon.md new file mode 100644 index 000000000..b4c9c9d6b --- /dev/null +++ b/doc/addon.md @@ -0,0 +1,518 @@ +# Add-on Structure + +Creating add-ons that work correctly when loaded multiple times from the same +source package into multiple Node.js threads and/or multiple times into the same +Node.js thread requires that all global data they hold be associated with the +environment in which they run. It is not safe to store global data in static +variables because doing so does not take into account the fact that an add-on +may be loaded into multiple threads nor that an add-on may be loaded multiple +times into a single thread. + +The `Napi::Addon` class can be used to define an entire add-on. Instances of +`Napi::Addon` subclasses become instances of the add-on, stored safely by +Node.js on its various threads and into its various contexts. Thus, any data +stored in the instance variables of a `Napi::Addon` subclass instance are stored +safely by Node.js. Functions exposed to JavaScript using +`Napi::Addon::InstanceMethod` and/or `Napi::Addon::DefineAddon` are instance +methods of the `Napi::Addon` subclass and thus have access to data stored inside +the instance. + +`Napi::Addon::DefineProperties` may be used to attach `Napi::Addon` subclass +instance methods to objects other than the one that will be returned to Node.js +as the add-on instance. + +The `Napi::Addon` class can be used together with the `NODE_API_ADDON()` and +`NODE_API_NAMED_ADDON()` macros to define add-ons. + +## Example + +```cpp +#include + +class ExampleAddon : public Napi::Addon { + public: + ExampleAddon(Napi::Env env, Napi::Object exports) { + // In the constructor we declare the functions the add-on makes avaialable + // to JavaScript. + DefineAddon(exports, { + InstanceMethod("increment", &ExampleAddon::Increment), + + // We can also attach plain objects to `exports`, and instance methods as + // properties of those sub-objects. + InstanceValue("subObject", DefineProperties(Napi::Object::New(), { + InstanceMethod("decrement", &ExampleAddon::Decrement + })), napi_enumerable) + }); + } + private: + + // This method has access to the data stored in the environment because it is + // an instance method of `ExampleAddon` and because it was listed among the + // property descriptors passed to `DefineAddon()` in the constructor. + Napi::Value Increment(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), ++value); + } + + // This method has access to the data stored in the environment because it is + // an instance method of `ExampleAddon` and because it was exposed to + // JavaScript by calling `DefineProperties()` with the object onto which it is + // attached. + Napi::Value Decrement(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), --value); + } + + // Data stored in these variables is unique to each instance of the add-on. + uint32_t value = 42; +}; + +// The macro announces that instances of the class `ExampleAddon` will be +// created for each instance of the add-on that must be loaded into Node.js. +NODE_API_ADDON(ExampleAddon) +``` + +The above code can be used from JavaScript as follows: + +```js +'use strict' + +const exampleAddon = require('bindings')('example_addon'); +console.log(exampleAddon.increment()); // prints 43 +console.log(exampleAddon.increment()); // prints 44 +consnole.log(exampleAddon.subObject.decrement()); // prints 43 +``` + +When Node.js loads an instance of the add-on, a new instance of the class is +created. Its constructor receives the environment `Napi::Env env` and the +exports object `Napi::Object exports`. It can then use the method `DefineAddon` +to either attach methods, accessors, and/or values to the `exports` object or to +create its own `exports` object and attach methods, accessors, and/or values to +it. + +Functions created with `Napi::Function::New()`, accessors created with +`PropertyDescriptor::Accessor()`, and values can also be attached. If their +implementation requires the `ExampleAddon` instance, it can be retrieved from +the `Napi::Env env` with `GetInstanceData()`: + +```cpp +void ExampleBinding(const Napi::CallbackInfo& info) { + ExampleAddon* addon = info.Env().GetInstanceData(); +} +``` + +## Methods + +### Constructor + +Creates a new instance of the add-on. + +```cpp +Napi::Addon(Napi::Env env, Napi::Object exports); +``` + +- `[in] env`: The environment into which the add-on is being loaded. +- `[in] exports`: The exports object received from JavaScript. + +Typically, the constructor calls `DefineAddon()` to attach methods, accessors, +and/or values to `exports`. The constructor may also create a new object and +pass it to `DefineAddon()` as its first parameter if it wishes to replace the +`exports` object as provided by Node.js. + +### DefineAddon + +Defines an add-on instance with functions, accessors, and/or values. + +```cpp +void Napi::Addon::DefineAddon(Napi::Object exports, + const std::initializer_list& properties); +``` + +* `[in] exports`: The object to return to Node.js as an instance of the add-on. +* `[in] properties`: Initializer list of add-on property descriptors of the +methods, property accessors, and values that define the add-on. They will be +set on `exports`. +See: [`Class property and descriptor`](class_property_descriptor.md). + +### DefineProperties + +Defines function, accessor, and/or value properties on an object using add-on +instance methods. + +```cpp +Napi::Object +Napi::Addon::DefineProperties(Napi::Object object, + const std::initializer_list& properties); +``` + +* `[in] object`: The object that will receive the new properties. +* `[in] properties`: Initializer list of property descriptors of the methods, +property accessors, and values to attach to `object`. +See: [`Class property and descriptor`](class_property_descriptor.md). + +Returns `object`. + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +void MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +Napi::Value MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(Napi::Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: JavaScript symbol that represents the name of the method provided +by the add-on. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +void MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(Napi::Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: JavaScript symbol that represents the name of the method provided +by the add-on. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +Napi::Value MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +void MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +Napi::Value MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(Napi::Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance method for the class. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +void MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(Napi::Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance method for the class. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +Napi::Value MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceAccessor + +Creates a property descriptor that represents an instance accessor property +provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceAccessor(const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] getter`: The native function to call when a get access to the property +is performed. +- `[in] setter`: The native function to call when a set access to the property +is performed. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the getter or the setter when it +is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents an instance accessor +property provided by the add-on. + +### InstanceAccessor + +Creates a property descriptor that represents an instance accessor property +provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceAccessor(Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance accessor. +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the getter or the setter when it +is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents an instance accessor +property provided by the add-on. + +### InstanceAccessor + +Creates a property descriptor that represents an instance accessor property +provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceAccessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the getter or the setter when it +is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents an instance accessor +property provided by the add-on. + +### InstanceAccessor + +Creates a property descriptor that represents an instance accessor property +provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceAccessor(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance accessor. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the getter or the setter when it +is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents an instance accessor +property provided by the add-on. + +### InstanceValue + +Creates property descriptor that represents an instance value property provided +by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceValue(const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the property. +- `[in] value`: The value that's retrieved by a get access of the property. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. + +Returns a `Napi::PropertyDescriptor` object that represents an instance value +property of an add-on. + +### InstanceValue + +Creates property descriptor that represents an instance value property provided +by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +name of the property. +- `[in] value`: The value that's retrieved by a get access of the property. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. + +Returns a `Napi::PropertyDescriptor` object that represents an instance value +property of an add-on. diff --git a/napi-inl.h b/napi-inl.h index 99ed3e3d3..a18ffcd59 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -238,6 +238,7 @@ struct AccessorCallbackData { // Module registration //////////////////////////////////////////////////////////////////////////////// +// Register an add-on based on an initializer function. #define NODE_API_MODULE(modname, regfunc) \ napi_value __napi_ ## regfunc(napi_env env, \ napi_value exports) { \ @@ -245,6 +246,20 @@ struct AccessorCallbackData { } \ NAPI_MODULE(modname, __napi_ ## regfunc) +// Register an add-on based on a subclass of `Addon` with a custom Node.js +// module name. +#define NODE_API_NAMED_ADDON(modname, classname) \ + static napi_value __napi_ ## classname(napi_env env, \ + napi_value exports) { \ + return Napi::RegisterModule(env, exports, &classname::Init); \ + } \ + NAPI_MODULE(modname, __napi_ ## classname) + +// Register an add-on based on a subclass of `Addon` with the Node.js module +// name given by node-gyp from the `target_name` in binding.gyp. +#define NODE_API_ADDON(classname) \ + NODE_API_NAMED_ADDON(NODE_GYP_MODULE_NAME, classname) + // Adapt the NAPI_MODULE registration function: // - Wrap the arguments in NAPI wrappers. // - Catch any NAPI errors and rethrow as JS exceptions. @@ -3143,419 +3158,553 @@ inline PropertyDescriptor::operator const napi_property_descriptor&() const { } //////////////////////////////////////////////////////////////////////////////// -// ObjectWrap class +// InstanceWrap class //////////////////////////////////////////////////////////////////////////////// template -inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { - napi_env env = callbackInfo.Env(); - napi_value wrapper = callbackInfo.This(); - napi_status status; - napi_ref ref; - T* instance = static_cast(this); - status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); - NAPI_THROW_IF_FAILED_VOID(env, status); - - Reference* instanceRef = instance; - *instanceRef = Reference(env, ref); -} - -template -inline ObjectWrap::~ObjectWrap() { - // If the JS object still exists at this point, remove the finalizer added - // through `napi_wrap()`. - if (!IsEmpty()) { - Object object = Value(); - // It is not valid to call `napi_remove_wrap()` with an empty `object`. - // This happens e.g. during garbage collection. - if (!object.IsEmpty() && _construction_failed) { - napi_remove_wrap(Env(), object, nullptr); - } - } -} - -template -inline T* ObjectWrap::Unwrap(Object wrapper) { - T* unwrapped; - napi_status status = napi_unwrap(wrapper.Env(), wrapper, reinterpret_cast(&unwrapped)); - NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); - return unwrapped; -} - -template -inline Function -ObjectWrap::DefineClass(Napi::Env env, - const char* utf8name, - const size_t props_count, - const napi_property_descriptor* descriptors, - void* data) { +inline void InstanceWrap::AttachPropData(napi_env env, + napi_value value, + const napi_property_descriptor* prop) { napi_status status; - std::vector props(props_count); - - // We copy the descriptors to a local array because before defining the class - // we must replace static method property descriptors with value property - // descriptors such that the value is a function-valued `napi_value` created - // with `CreateFunction()`. - // - // This replacement could be made for instance methods as well, but V8 aborts - // if we do that, because it expects methods defined on the prototype template - // to have `FunctionTemplate`s. - for (size_t index = 0; index < props_count; index++) { - props[index] = descriptors[index]; - napi_property_descriptor* prop = &props[index]; - if (prop->method == T::StaticMethodCallbackWrapper) { - status = CreateFunction(env, - utf8name, - prop->method, - static_cast(prop->data), - &(prop->value)); - NAPI_THROW_IF_FAILED(env, status, Function()); - prop->method = nullptr; - prop->data = nullptr; - } else if (prop->method == T::StaticVoidMethodCallbackWrapper) { - status = CreateFunction(env, - utf8name, - prop->method, - static_cast(prop->data), - &(prop->value)); - NAPI_THROW_IF_FAILED(env, status, Function()); - prop->method = nullptr; - prop->data = nullptr; - } - } - - napi_value value; - status = napi_define_class(env, - utf8name, - NAPI_AUTO_LENGTH, - T::ConstructorCallbackWrapper, - data, - props_count, - props.data(), - &value); - NAPI_THROW_IF_FAILED(env, status, Function()); - - // After defining the class we iterate once more over the property descriptors - // and attach the data associated with accessors and instance methods to the - // newly created JavaScript class. - for (size_t idx = 0; idx < props_count; idx++) { - const napi_property_descriptor* prop = &props[idx]; - - if (prop->getter == T::StaticGetterCallbackWrapper || - prop->setter == T::StaticSetterCallbackWrapper) { + if (prop->method != nullptr && !(prop->attributes & napi_static)) { + if (prop->method == T::InstanceVoidMethodCallbackWrapper) { status = Napi::details::AttachData(env, - value, - static_cast(prop->data)); - NAPI_THROW_IF_FAILED(env, status, Function()); + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); + } else if (prop->method == T::InstanceMethodCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); } else if (prop->getter == T::InstanceGetterCallbackWrapper || prop->setter == T::InstanceSetterCallbackWrapper) { status = Napi::details::AttachData(env, value, static_cast(prop->data)); - NAPI_THROW_IF_FAILED(env, status, Function()); - } else if (prop->method != nullptr && !(prop->attributes & napi_static)) { - if (prop->method == T::InstanceVoidMethodCallbackWrapper) { - status = Napi::details::AttachData(env, - value, - static_cast(prop->data)); - NAPI_THROW_IF_FAILED(env, status, Function()); - } else if (prop->method == T::InstanceMethodCallbackWrapper) { - status = Napi::details::AttachData(env, - value, - static_cast(prop->data)); - NAPI_THROW_IF_FAILED(env, status, Function()); - } + NAPI_THROW_IF_FAILED_VOID(env, status); } } - - return Function(env, value); -} - -template -inline Function ObjectWrap::DefineClass( - Napi::Env env, - const char* utf8name, - const std::initializer_list>& properties, - void* data) { - return DefineClass(env, - utf8name, - properties.size(), - reinterpret_cast(properties.begin()), - data); -} - -template -inline Function ObjectWrap::DefineClass( - Napi::Env env, - const char* utf8name, - const std::vector>& properties, - void* data) { - return DefineClass(env, - utf8name, - properties.size(), - reinterpret_cast(properties.data()), - data); } template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( const char* utf8name, - StaticVoidMethodCallback method, + InstanceVoidMethodCallback method, napi_property_attributes attributes, void* data) { - StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); + InstanceVoidMethodCallbackData* callbackData = + new InstanceVoidMethodCallbackData({ method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = T::StaticVoidMethodCallbackWrapper; + desc.method = T::InstanceVoidMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( const char* utf8name, - StaticMethodCallback method, + InstanceMethodCallback method, napi_property_attributes attributes, void* data) { - StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); + InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = T::StaticMethodCallbackWrapper; + desc.method = T::InstanceMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( Symbol name, - StaticVoidMethodCallback method, + InstanceVoidMethodCallback method, napi_property_attributes attributes, void* data) { - StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); + InstanceVoidMethodCallbackData* callbackData = + new InstanceVoidMethodCallbackData({ method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = T::StaticVoidMethodCallbackWrapper; + desc.method = T::InstanceVoidMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( Symbol name, - StaticMethodCallback method, + InstanceMethodCallback method, napi_property_attributes attributes, void* data) { - StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); + InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = T::StaticMethodCallbackWrapper; + desc.method = T::InstanceMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticVoidMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = &ObjectWrap::WrappedMethod; + desc.method = &InstanceWrap::WrappedMethod; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticVoidMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - Symbol name, +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.method = &ObjectWrap::WrappedMethod; + desc.utf8name = utf8name; + desc.method = &InstanceWrap::WrappedMethod; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - const char* utf8name, +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.method = &ObjectWrap::WrappedMethod; + desc.name = name; + desc.method = &InstanceWrap::WrappedMethod; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = &ObjectWrap::WrappedMethod; + desc.method = &InstanceWrap::WrappedMethod; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( const char* utf8name, - StaticGetterCallback getter, - StaticSetterCallback setter, + InstanceGetterCallback getter, + InstanceSetterCallback setter, napi_property_attributes attributes, void* data) { - StaticAccessorCallbackData* callbackData = - new StaticAccessorCallbackData({ getter, setter, data }); + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({ getter, setter, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( Symbol name, - StaticGetterCallback getter, - StaticSetterCallback setter, + InstanceGetterCallback getter, + InstanceSetterCallback setter, napi_property_attributes attributes, void* data) { - StaticAccessorCallbackData* callbackData = - new StaticAccessorCallbackData({ getter, setter, data }); + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({ getter, setter, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticGetterCallback getter, - typename ObjectWrap::StaticSetterCallback setter> -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( +template ::InstanceGetterCallback getter, + typename InstanceWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); - desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.getter = This::WrapGetter(This::GetterTag()); + desc.setter = This::WrapSetter(This::SetterTag()); desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticGetterCallback getter, - typename ObjectWrap::StaticSetterCallback setter> -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( +template ::InstanceGetterCallback getter, + typename InstanceWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); - desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.getter = This::WrapGetter(This::GetterTag()); + desc.setter = This::WrapSetter(This::SetterTag()); desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( +inline ClassPropertyDescriptor InstanceWrap::InstanceValue( const char* utf8name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes, - void* data) { - InstanceVoidMethodCallbackData* callbackData = - new InstanceVoidMethodCallbackData({ method, data}); - + Napi::Value value, + napi_property_attributes attributes) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = T::InstanceVoidMethodCallbackWrapper; - desc.data = callbackData; + desc.value = value; desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( - const char* utf8name, - InstanceMethodCallback method, - napi_property_attributes attributes, - void* data) { - InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); +inline ClassPropertyDescriptor InstanceWrap::InstanceValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = attributes; + return desc; +} + +template +inline napi_value InstanceWrap::InstanceVoidMethodCallbackWrapper( + napi_env env, + napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceVoidMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->callback; + (instance->*cb)(callbackInfo); + return nullptr; + }); +} + +template +inline napi_value InstanceWrap::InstanceMethodCallbackWrapper( + napi_env env, + napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->callback; + return (instance->*cb)(callbackInfo); + }); +} + +template +inline napi_value InstanceWrap::InstanceGetterCallbackWrapper( + napi_env env, + napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->getterCallback; + return (instance->*cb)(callbackInfo); + }); +} + +template +inline napi_value InstanceWrap::InstanceSetterCallbackWrapper( + napi_env env, + napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->setterCallback; + (instance->*cb)(callbackInfo, callbackInfo[0]); + return nullptr; + }); +} + +template +template ::InstanceVoidMethodCallback method> +inline napi_value InstanceWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + (instance->*method)(cbInfo); + return nullptr; + }); +} + +template +template ::InstanceMethodCallback method> +inline napi_value InstanceWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + return (instance->*method)(cbInfo); + }); +} + +template +template ::InstanceSetterCallback method> +inline napi_value InstanceWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + (instance->*method)(cbInfo, cbInfo[0]); + return nullptr; + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// ObjectWrap class +//////////////////////////////////////////////////////////////////////////////// + +template +inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { + napi_env env = callbackInfo.Env(); + napi_value wrapper = callbackInfo.This(); + napi_status status; + napi_ref ref; + T* instance = static_cast(this); + status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); + NAPI_THROW_IF_FAILED_VOID(env, status); + + Reference* instanceRef = instance; + *instanceRef = Reference(env, ref); +} + +template +inline ObjectWrap::~ObjectWrap() { + // If the JS object still exists at this point, remove the finalizer added + // through `napi_wrap()`. + if (!IsEmpty()) { + Object object = Value(); + // It is not valid to call `napi_remove_wrap()` with an empty `object`. + // This happens e.g. during garbage collection. + if (!object.IsEmpty() && _construction_failed) { + napi_remove_wrap(Env(), object, nullptr); + } + } +} + +template +inline T* ObjectWrap::Unwrap(Object wrapper) { + T* unwrapped; + napi_status status = napi_unwrap(wrapper.Env(), wrapper, reinterpret_cast(&unwrapped)); + NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); + return unwrapped; +} + +template +inline Function +ObjectWrap::DefineClass(Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* descriptors, + void* data) { + napi_status status; + std::vector props(props_count); + + // We copy the descriptors to a local array because before defining the class + // we must replace static method property descriptors with value property + // descriptors such that the value is a function-valued `napi_value` created + // with `CreateFunction()`. + // + // This replacement could be made for instance methods as well, but V8 aborts + // if we do that, because it expects methods defined on the prototype template + // to have `FunctionTemplate`s. + for (size_t index = 0; index < props_count; index++) { + props[index] = descriptors[index]; + napi_property_descriptor* prop = &props[index]; + if (prop->method == T::StaticMethodCallbackWrapper) { + status = CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } else if (prop->method == T::StaticVoidMethodCallbackWrapper) { + status = CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } + } + + napi_value value; + status = napi_define_class(env, + utf8name, + NAPI_AUTO_LENGTH, + T::ConstructorCallbackWrapper, + data, + props_count, + props.data(), + &value); + NAPI_THROW_IF_FAILED(env, status, Function()); + + // After defining the class we iterate once more over the property descriptors + // and attach the data associated with accessors and instance methods to the + // newly created JavaScript class. + for (size_t idx = 0; idx < props_count; idx++) { + const napi_property_descriptor* prop = &props[idx]; + + if (prop->getter == T::StaticGetterCallbackWrapper || + prop->setter == T::StaticSetterCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } else { + // InstanceWrap::AttachPropData is responsible for attaching the data + // of instance methods and accessors. + T::AttachPropData(env, value, prop); + } + } + + return Function(env, value); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const std::initializer_list>& properties, + void* data) { + return DefineClass(env, + utf8name, + properties.size(), + reinterpret_cast(properties.begin()), + data); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const std::vector>& properties, + void* data) { + return DefineClass(env, + utf8name, + properties.size(), + reinterpret_cast(properties.data()), + data); +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + StaticVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = T::InstanceMethodCallbackWrapper; + desc.method = T::StaticVoidMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + StaticMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::StaticMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( Symbol name, - InstanceVoidMethodCallback method, + StaticVoidMethodCallback method, napi_property_attributes attributes, void* data) { - InstanceVoidMethodCallbackData* callbackData = - new InstanceVoidMethodCallbackData({ method, data}); + StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = T::InstanceVoidMethodCallbackWrapper; + desc.method = T::StaticVoidMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( Symbol name, - InstanceMethodCallback method, + StaticMethodCallback method, napi_property_attributes attributes, void* data) { - InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); + StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = T::InstanceMethodCallbackWrapper; + desc.method = T::StaticMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceVoidMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( const char* utf8name, napi_property_attributes attributes, void* data) { @@ -3563,41 +3712,41 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( desc.utf8name = utf8name; desc.method = &ObjectWrap::WrappedMethod; desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( - const char* utf8name, +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; + desc.name = name; desc.method = &ObjectWrap::WrappedMethod; desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceVoidMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( - Symbol name, +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; + desc.utf8name = utf8name; desc.method = &ObjectWrap::WrappedMethod; desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( Symbol name, napi_property_attributes attributes, void* data) { @@ -3605,77 +3754,77 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( desc.name = name; desc.method = &ObjectWrap::WrappedMethod; desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( const char* utf8name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, + StaticGetterCallback getter, + StaticSetterCallback setter, napi_property_attributes attributes, void* data) { - InstanceAccessorCallbackData* callbackData = - new InstanceAccessorCallbackData({ getter, setter, data }); + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({ getter, setter, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; desc.data = callbackData; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( Symbol name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, + StaticGetterCallback getter, + StaticSetterCallback setter, napi_property_attributes attributes, void* data) { - InstanceAccessorCallbackData* callbackData = - new InstanceAccessorCallbackData({ getter, setter, data }); + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({ getter, setter, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; desc.data = callbackData; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceGetterCallback getter, - typename ObjectWrap::InstanceSetterCallback setter> -inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = This::WrapGetter(This::GetterTag()); - desc.setter = This::WrapSetter(This::SetterTag()); + desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceGetterCallback getter, - typename ObjectWrap::InstanceSetterCallback setter> -inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = This::WrapGetter(This::GetterTag()); - desc.setter = This::WrapSetter(This::SetterTag()); + desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } @@ -3699,30 +3848,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticValue(Symbol name, return desc; } -template -inline ClassPropertyDescriptor ObjectWrap::InstanceValue( - const char* utf8name, - Napi::Value value, - napi_property_attributes attributes) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.value = value; - desc.attributes = attributes; - return desc; -} - -template -inline ClassPropertyDescriptor ObjectWrap::InstanceValue( - Symbol name, - Napi::Value value, - napi_property_attributes attributes) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.value = value; - desc.attributes = attributes; - return desc; -} - template inline void ObjectWrap::Finalize(Napi::Env /*env*/) {} @@ -3815,68 +3940,6 @@ inline napi_value ObjectWrap::StaticSetterCallbackWrapper( }); } -template -inline napi_value ObjectWrap::InstanceVoidMethodCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { - CallbackInfo callbackInfo(env, info); - InstanceVoidMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = Unwrap(callbackInfo.This().As()); - auto cb = callbackData->callback; - (instance->*cb)(callbackInfo); - return nullptr; - }); -} - -template -inline napi_value ObjectWrap::InstanceMethodCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { - CallbackInfo callbackInfo(env, info); - InstanceMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = Unwrap(callbackInfo.This().As()); - auto cb = callbackData->callback; - return (instance->*cb)(callbackInfo); - }); -} - -template -inline napi_value ObjectWrap::InstanceGetterCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { - CallbackInfo callbackInfo(env, info); - InstanceAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = Unwrap(callbackInfo.This().As()); - auto cb = callbackData->getterCallback; - return (instance->*cb)(callbackInfo); - }); -} - -template -inline napi_value ObjectWrap::InstanceSetterCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { - CallbackInfo callbackInfo(env, info); - InstanceAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = Unwrap(callbackInfo.This().As()); - auto cb = callbackData->setterCallback; - (instance->*cb)(callbackInfo, callbackInfo[0]); - return nullptr; - }); -} - template inline void ObjectWrap::FinalizeCallback(napi_env env, void* data, void* /*hint*/) { T* instance = static_cast(data); @@ -3901,27 +3964,6 @@ inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info }); } -template -template ::InstanceVoidMethodCallback method> -inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { - const CallbackInfo cbInfo(env, info); - T* instance = Unwrap(cbInfo.This().As()); - (instance->*method)(cbInfo); - return nullptr; - }); -} - -template -template ::InstanceMethodCallback method> -inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { - const CallbackInfo cbInfo(env, info); - T* instance = Unwrap(cbInfo.This().As()); - return (instance->*method)(cbInfo); - }); -} - template template ::StaticSetterCallback method> inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { @@ -3932,17 +3974,6 @@ inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info }); } -template -template ::InstanceSetterCallback method> -inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { - const CallbackInfo cbInfo(env, info); - T* instance = Unwrap(cbInfo.This().As()); - (instance->*method)(cbInfo, cbInfo[0]); - return nullptr; - }); -} - //////////////////////////////////////////////////////////////////////////////// // HandleScope class //////////////////////////////////////////////////////////////////////////////// @@ -5000,6 +5031,49 @@ inline const napi_node_version* VersionManagement::GetNodeVersion(Env env) { return result; } +#if NAPI_VERSION > 5 +//////////////////////////////////////////////////////////////////////////////// +// Addon class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Object Addon::Init(Env env, Object exports) { + T* addon = new T(env, exports); + env.SetInstanceData(addon); + return addon->entry_point_; +} + +template +inline T* Addon::Unwrap(Object wrapper) { + return wrapper.Env().GetInstanceData(); +} + +template +inline void +Addon::DefineAddon(Object exports, + const std::initializer_list& props) { + DefineProperties(exports, props); + entry_point_ = exports; +} + +template +inline Napi::Object +Addon::DefineProperties(Object object, + const std::initializer_list& props) { + const napi_property_descriptor* properties = + reinterpret_cast(props.begin()); + size_t size = props.size(); + napi_status status = napi_define_properties(object.Env(), + object, + size, + properties); + NAPI_THROW_IF_FAILED(object.Env(), status, object); + for (size_t idx = 0; idx < size; idx++) + T::AttachPropData(object.Env(), object, &properties[idx]); + return object; +} +#endif // NAPI_VERSION > 5 + } // namespace Napi #endif // SRC_NAPI_INL_H_ diff --git a/napi.h b/napi.h index 6c80efc26..cf0ce51e7 100644 --- a/napi.h +++ b/napi.h @@ -1647,6 +1647,122 @@ namespace Napi { napi_property_descriptor _desc; }; + template + struct MethodCallbackData { + TCallback callback; + void* data; + }; + + template + struct AccessorCallbackData { + TGetterCallback getterCallback; + TSetterCallback setterCallback; + void* data; + }; + + template + class InstanceWrap { + public: + + typedef void (T::*InstanceVoidMethodCallback)(const CallbackInfo& info); + typedef Napi::Value (T::*InstanceMethodCallback)(const CallbackInfo& info); + typedef Napi::Value (T::*InstanceGetterCallback)(const CallbackInfo& info); + typedef void (T::*InstanceSetterCallback)(const CallbackInfo& info, const Napi::Value& value); + + typedef ClassPropertyDescriptor PropertyDescriptor; + + static PropertyDescriptor InstanceMethod(const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod(const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod(Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod(Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor(const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor(Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceValue(const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor InstanceValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + + protected: + static void AttachPropData(napi_env env, napi_value value, const napi_property_descriptor* prop); + + private: + using This = InstanceWrap; + + typedef MethodCallbackData InstanceVoidMethodCallbackData; + typedef MethodCallbackData InstanceMethodCallbackData; + typedef AccessorCallbackData InstanceAccessorCallbackData; + + static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, napi_callback_info info); + static napi_value InstanceMethodCallbackWrapper(napi_env env, napi_callback_info info); + static napi_value InstanceGetterCallbackWrapper(napi_env env, napi_callback_info info); + static napi_value InstanceSetterCallbackWrapper(napi_env env, napi_callback_info info); + + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + + template struct GetterTag {}; + template struct SetterTag {}; + + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + template + static napi_callback WrapGetter(GetterTag) noexcept { return &This::WrappedMethod; } + static napi_callback WrapGetter(GetterTag) noexcept { return nullptr; } + template + static napi_callback WrapSetter(SetterTag) noexcept { return &This::WrappedMethod; } + static napi_callback WrapSetter(SetterTag) noexcept { return nullptr; } + }; + /// Base class to be extended by C++ classes exposed to JavaScript; each C++ class instance gets /// "wrapped" by a JavaScript object that is managed by this class. /// @@ -1673,7 +1789,7 @@ namespace Napi { /// Napi::Value DoSomething(const Napi::CallbackInfo& info); /// } template - class ObjectWrap : public Reference { + class ObjectWrap : public InstanceWrap, public Reference { public: ObjectWrap(const CallbackInfo& callbackInfo); virtual ~ObjectWrap(); @@ -1685,10 +1801,6 @@ namespace Napi { typedef Napi::Value (*StaticMethodCallback)(const CallbackInfo& info); typedef Napi::Value (*StaticGetterCallback)(const CallbackInfo& info); typedef void (*StaticSetterCallback)(const CallbackInfo& info, const Napi::Value& value); - typedef void (T::*InstanceVoidMethodCallback)(const CallbackInfo& info); - typedef Napi::Value (T::*InstanceMethodCallback)(const CallbackInfo& info); - typedef Napi::Value (T::*InstanceGetterCallback)(const CallbackInfo& info); - typedef void (T::*InstanceSetterCallback)(const CallbackInfo& info, const Napi::Value& value); typedef ClassPropertyDescriptor PropertyDescriptor; @@ -1750,68 +1862,12 @@ namespace Napi { static PropertyDescriptor StaticAccessor(Symbol name, napi_property_attributes attributes = napi_default, void* data = nullptr); - static PropertyDescriptor InstanceMethod(const char* utf8name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod(const char* utf8name, - InstanceMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod(Symbol name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod(Symbol name, - InstanceMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceAccessor(const char* utf8name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceAccessor(Symbol name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceAccessor(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceAccessor(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); static PropertyDescriptor StaticValue(const char* utf8name, Napi::Value value, napi_property_attributes attributes = napi_default); static PropertyDescriptor StaticValue(Symbol name, Napi::Value value, napi_property_attributes attributes = napi_default); - static PropertyDescriptor InstanceValue(const char* utf8name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor InstanceValue(Symbol name, - Napi::Value value, - napi_property_attributes attributes = napi_default); virtual void Finalize(Napi::Env env); private: @@ -1822,10 +1878,6 @@ namespace Napi { static napi_value StaticMethodCallbackWrapper(napi_env env, napi_callback_info info); static napi_value StaticGetterCallbackWrapper(napi_env env, napi_callback_info info); static napi_value StaticSetterCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value InstanceMethodCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value InstanceGetterCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value InstanceSetterCallbackWrapper(napi_env env, napi_callback_info info); static void FinalizeCallback(napi_env env, void* data, void* hint); static Function DefineClass(Napi::Env env, const char* utf8name, @@ -1833,26 +1885,12 @@ namespace Napi { const napi_property_descriptor* props, void* data = nullptr); - template - struct MethodCallbackData { - TCallback callback; - void* data; - }; - typedef MethodCallbackData StaticVoidMethodCallbackData; - typedef MethodCallbackData StaticMethodCallbackData; - typedef MethodCallbackData InstanceVoidMethodCallbackData; - typedef MethodCallbackData InstanceMethodCallbackData; - - template - struct AccessorCallbackData { - TGetterCallback getterCallback; - TSetterCallback setterCallback; - void* data; - }; - typedef AccessorCallbackData - StaticAccessorCallbackData; - typedef AccessorCallbackData - InstanceAccessorCallbackData; + typedef MethodCallbackData StaticVoidMethodCallbackData; + typedef MethodCallbackData StaticMethodCallbackData; + + typedef AccessorCallbackData StaticAccessorCallbackData; template static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; @@ -1860,22 +1898,11 @@ namespace Napi { template static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - template static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - - template struct StaticGetterTag {}; - template struct StaticSetterTag {}; - template struct GetterTag {}; - template struct SetterTag {}; + template struct StaticGetterTag {}; + template struct StaticSetterTag {}; template static napi_callback WrapStaticGetter(StaticGetterTag) noexcept { return &This::WrappedMethod; } @@ -1885,14 +1912,6 @@ namespace Napi { static napi_callback WrapStaticSetter(StaticSetterTag) noexcept { return &This::WrappedMethod; } static napi_callback WrapStaticSetter(StaticSetterTag) noexcept { return nullptr; } - template - static napi_callback WrapGetter(GetterTag) noexcept { return &This::WrappedMethod; } - static napi_callback WrapGetter(GetterTag) noexcept { return nullptr; } - - template - static napi_callback WrapSetter(SetterTag) noexcept { return &This::WrappedMethod; } - static napi_callback WrapSetter(SetterTag) noexcept { return nullptr; } - bool _construction_failed = true; }; @@ -2420,6 +2439,25 @@ namespace Napi { static const napi_node_version* GetNodeVersion(Env env); }; +#if NAPI_VERSION > 5 + template + class Addon : public InstanceWrap { + public: + static inline Object Init(Env env, Object exports); + static T* Unwrap(Object wrapper); + + protected: + typedef ClassPropertyDescriptor AddonProp; + void DefineAddon(Object exports, + const std::initializer_list& props); + Napi::Object DefineProperties(Object object, + const std::initializer_list& props); + + private: + Object entry_point_; + }; +#endif // NAPI_VERSION > 5 + } // namespace Napi // Inline implementations of all the above class methods are included here. diff --git a/package.json b/package.json index b357f07b3..64a44f32a 100644 --- a/package.json +++ b/package.json @@ -252,6 +252,7 @@ "description": "Node.js API (N-API)", "devDependencies": { "benchmark": "^2.1.4", + "bindings": "^1.5.0", "safe-buffer": "^5.1.1" }, "directories": {}, diff --git a/test/addon.cc b/test/addon.cc new file mode 100644 index 000000000..9652f9aa4 --- /dev/null +++ b/test/addon.cc @@ -0,0 +1,36 @@ +#if (NAPI_VERSION > 5) +#include +#include "napi.h" + +namespace { + +class TestAddon : public Napi::Addon { + public: + inline TestAddon(Napi::Env env, Napi::Object exports) { + DefineAddon(exports, { + InstanceMethod("increment", &TestAddon::Increment), + InstanceValue("subObject", DefineProperties(Napi::Object::New(env), { + InstanceMethod("decrement", &TestAddon::Decrement) + })) + }); + } + + private: + Napi::Value Increment(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), ++value); + } + + Napi::Value Decrement(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), --value); + } + + uint32_t value = 42; +}; + +} // end of anonymous namespace + +Napi::Object InitAddon(Napi::Env env) { + return TestAddon::Init(env, Napi::Object::New(env)); +} + +#endif // (NAPI_VERSION > 5) diff --git a/test/addon.js b/test/addon.js new file mode 100644 index 000000000..54a5f666a --- /dev/null +++ b/test/addon.js @@ -0,0 +1,12 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + assert.strictEqual(binding.addon.increment(), 43); + assert.strictEqual(binding.addon.increment(), 44); + assert.strictEqual(binding.addon.subObject.decrement(), 43); +} diff --git a/test/binding.cc b/test/binding.cc index 0eb22abbf..ebfe5e5db 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -3,6 +3,7 @@ using namespace Napi; #if (NAPI_VERSION > 5) +Object InitAddon(Env env); Object InitAddonData(Env env); #endif Object InitArrayBuffer(Env env); @@ -61,6 +62,7 @@ Object InitThunkingManual(Env env); Object Init(Env env, Object exports) { #if (NAPI_VERSION > 5) + exports.Set("addon", InitAddon(env)); exports.Set("addon_data", InitAddonData(env)); #endif exports.Set("arraybuffer", InitArrayBuffer(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 797d81139..288051633 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -2,6 +2,7 @@ 'target_defaults': { 'includes': ['../common.gypi'], 'sources': [ + 'addon.cc', 'addon_data.cc', 'arraybuffer.cc', 'asynccontext.cc', diff --git a/test/index.js b/test/index.js index e930eab5c..137cad238 100644 --- a/test/index.js +++ b/test/index.js @@ -8,6 +8,7 @@ process.config.target_defaults.default_configuration = // FIXME: We might need a way to load test modules automatically without // explicit declaration as follows. let testModules = [ + 'addon', 'addon_data', 'arraybuffer', 'asynccontext', @@ -83,9 +84,10 @@ if (napiVersion < 5) { } if (napiVersion < 6) { + testModules.splice(testModules.indexOf('addon'), 1); + testModules.splice(testModules.indexOf('addon_data'), 1); testModules.splice(testModules.indexOf('bigint'), 1); testModules.splice(testModules.indexOf('typedarray-bigint'), 1); - testModules.splice(testModules.indexOf('addon_data'), 1); } if (typeof global.gc === 'function') {