Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

n-api: add napi_define_subclass #36148

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions benchmark/napi/function_call/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ try {
}
const napi = napi_binding.hello;

const class_instance = new napi_binding.Class();
const subclass_instance = new napi_binding.Subclass();

let c = 0;
function js() {
return c++;
Expand All @@ -37,15 +40,25 @@ function js() {
assert(js() === cxx());

const bench = common.createBenchmark(main, {
type: ['js', 'cxx', 'napi'],
n: [1e6, 1e7, 5e7]
type: ['js', 'cxx', 'napi', 'napi_define_class', 'napi_define_subclass'],
n: [1e6, 1e7, 5e7, 1e8]
});

function main({ n, type }) {
const fn = type === 'cxx' ? cxx : type === 'napi' ? napi : js;
bench.start();
for (let i = 0; i < n; i++) {
fn();
if (type.match(/napi_define/)) {
const instance = (type === 'napi_define_class' ? class_instance :
subclass_instance);
bench.start();
for (let i = 0; i < n; i++) {
instance.fn();
}
bench.end(n);
} else {
const fn = type === 'cxx' ? cxx : type === 'napi' ? napi : js;
bench.start();
for (let i = 0; i < n; i++) {
fn();
}
bench.end(n);
}
bench.end(n);
}
36 changes: 35 additions & 1 deletion benchmark/napi/function_call/napi_binding.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <assert.h>
#define NAPI_EXPERIMENTAL
#include <node_api.h>

static int32_t increment = 0;
Expand All @@ -11,7 +12,9 @@ static napi_value Hello(napi_env env, napi_callback_info info) {
}

NAPI_MODULE_INIT() {
napi_value hello;
napi_value hello, klass, subklass;

// Create plain function and attach to exports.
napi_status status =
napi_create_function(env,
"hello",
Expand All @@ -22,5 +25,36 @@ NAPI_MODULE_INIT() {
assert(status == napi_ok);
status = napi_set_named_property(env, exports, "hello", hello);
assert(status == napi_ok);

// Create class and attach to exports.
napi_property_descriptor prop =
{ "fn", NULL, Hello, NULL, NULL, NULL, napi_enumerable, NULL };
status = napi_define_class(env,
"Class",
NAPI_AUTO_LENGTH,
Hello,
NULL,
1,
&prop,
&klass);
assert (status == napi_ok);
status = napi_set_named_property(env, exports, "Class", klass);
assert(status == napi_ok);

// Create subclass and attach to exports.
status = napi_define_subclass(env,
NULL,
"Class",
NAPI_AUTO_LENGTH,
Hello,
NULL,
NULL,
1,
&prop,
&subklass);
assert (status == napi_ok);
status = napi_set_named_property(env, exports, "Subclass", subklass);
assert(status == napi_ok);

return exports;
}
83 changes: 83 additions & 0 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4785,6 +4785,88 @@ with the resulting JavaScript constructor (which is returned in the `result`
parameter) and freed whenever the class is garbage-collected by passing both
the JavaScript function and the data to [`napi_add_finalizer`][].

### napi_define_subclass
<!-- YAML
added: REPLACEME
-->

```c
napi_status napi_define_subclass(napi_env env,
napi_value parent_constructor,
const char* utf8name,
size_t length,
napi_callback constructor,
napi_callback get_super_params,
void* data,
size_t property_count,
const napi_property_descriptor* properties,
napi_value* result);
```

* `[in] env`: The environment that the API is invoked under.
* `[in] parent_constructor`: Constructor of the parent class.
* `[in] utf8name`: Name of the JavaScript constructor function; this is
not required to be the same as the C++ class name, though it is recommended
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is talking about C++ everywhere, but C is what we mean, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I copied the doc mostly from napi_define_class, updating for the different functionality. I suppose it might be worth updating the doc for napi_define_class to not assume folks write C++ classes. I'd like to place that beyond the scope of this PR though.

#36150

for clarity.
* `[in] length`: The length of the `utf8name` in bytes, or `NAPI_AUTO_LENGTH`
if it is null-terminated.
* `[in] constructor`: Callback function that handles constructing instances
of the subclass. This should be a static method on the class, not an actual
C++ constructor function. [`napi_callback`][] provides more details.
* `[in] get_super_params`: Optional callback function that processes the
arguments that will be passed to the superclass before its constructor gets
called. It is called before the constructor and receives the parameters the
constructor will also receive. It must return a JavaScript array which may be
empty. The return value will be spread into the arguments list for the call to
the constructor of the superclass. Conceptually, this looks as follows:
```js
class SubclassExample extends SuperclassExample {
constructor(...args) {
super(...get_super_params.apply(null, args));
constructor.apply(this, args);
}
}
```
* `[in] data`: Optional data to be passed to the constructor callback as
the `data` property of the callback info.
* `[in] property_count`: Number of items in the `properties` array argument.
* `[in] properties`: Array of property descriptors describing static and
instance data properties, accessors, and methods on the class
See `napi_property_descriptor`.
* `[out] result`: A `napi_value` representing the constructor function for
the subclass.

Defines a subclass of a JavaScript class that corresponds to a C++ class,
including:

* A JavaScript constructor function that has the class name and invokes the
provided C++ constructor callback.
* A way to intercept and constructor parameters that will be passed to the
superclass constructor.
* Properties on the constructor function corresponding to _static_ data
properties, accessors, and methods of the C++ class (defined by
property descriptors with the `napi_static` attribute).
* Properties on the constructor function's `prototype` object corresponding to
_non-static_ data properties, accessors, and methods of the C++ class
(defined by property descriptors without the `napi_static` attribute).

The C++ constructor callback should be a static method on the class that calls
the actual class constructor, then wraps the new C++ instance in a JavaScript
object, and returns the wrapper object. See `napi_wrap()` for details.

The JavaScript constructor function returned from [`napi_define_subclass`][] is
often saved and used later, to construct new instances of the class from native
code, and/or to check whether provided values are instances of the class. In
that case, to prevent the function value from being garbage-collected, create a
persistent reference to it using [`napi_create_reference`][] and ensure the
reference count is kept >= 1.

Any non-`NULL` data which is passed to this API via the `data` parameter or via
the `data` field of the `napi_property_descriptor` array items can be associated
with the resulting JavaScript constructor (which is returned in the `result`
parameter) and freed whenever the class is garbage-collected by passing both
the JavaScript constructor and the data to [`napi_add_finalizer`][].

### napi_wrap
<!-- YAML
added: v8.0.0
Expand Down Expand Up @@ -6021,6 +6103,7 @@ This API may only be called from the main thread.
[`napi_create_reference`]: #n_api_napi_create_reference
[`napi_create_type_error`]: #n_api_napi_create_type_error
[`napi_define_class`]: #n_api_napi_define_class
[`napi_define_subclass`]: #n_api_napi_define_subclass
[`napi_delete_async_work`]: #n_api_napi_delete_async_work
[`napi_delete_reference`]: #n_api_napi_delete_reference
[`napi_escape_handle`]: #n_api_napi_escape_handle
Expand Down
12 changes: 12 additions & 0 deletions src/js_native_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,18 @@ NAPI_EXTERN napi_status napi_object_freeze(napi_env env,
napi_value object);
NAPI_EXTERN napi_status napi_object_seal(napi_env env,
napi_value object);

NAPI_EXTERN napi_status
napi_define_subclass(napi_env env,
napi_value parent_constructor,
const char* utf8name,
size_t length,
napi_callback constructor,
napi_callback get_super_params,
void* data,
size_t property_count,
const napi_property_descriptor* properties,
napi_value* child_ctor);
#endif // NAPI_EXPERIMENTAL

EXTERN_C_END
Expand Down
Loading