From 8ae083ca6c22fc642a849637eaaccfaedb699bfa Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Mon, 25 Nov 2019 09:14:50 -0800 Subject: [PATCH] src: add templated function factories These variants of `Napi::Function::New` accept the callback as a template parameter rather than a function parameter. This allows us to perform the binding without additional heap-allocation of the function callback data. --- doc/function.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++- napi-inl.h | 45 +++++++++++++++++++++ napi.h | 23 +++++++++++ test/function.cc | 27 ++++++++++++- test/function.js | 36 +++++++++-------- 5 files changed, 215 insertions(+), 19 deletions(-) diff --git a/doc/function.md b/doc/function.md index efc7ed495..889d5ea14 100644 --- a/doc/function.md +++ b/doc/function.md @@ -25,7 +25,7 @@ Value Fn(const CallbackInfo& info) { } Object Init(Env env, Object exports) { - exports.Set(String::New(env, "fn"), Function::New(env, Fn)); + exports.Set(String::New(env, "fn"), Function::New(env)); } NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) @@ -47,6 +47,27 @@ and in general in situations which don't have an existing JavaScript function on the stack. The `Call` method is used when there is already a JavaScript function on the stack (for example when running a native method called from JavaScript). +## Type definitions + +### Napi::Function::VoidCallback + +This is the type describing a callback returning `void` that will be invoked +from JavaScript. + +```cpp +typedef void (*VoidCallback)(const Napi::CallbackInfo& info); +``` + +### Napi::Function::Callback + +This is the type describing a callback returning a value that will be invoked +from JavaScript. + + +```cpp +typedef Value (*Callback)(const Napi::CallbackInfo& info); +``` + ## Methods ### Constructor @@ -74,6 +95,86 @@ Returns a non-empty `Napi::Function` instance. Creates an instance of a `Napi::Function` object. +```cpp +template +static Napi::Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); +``` + +- `[template] cb`: The native function to invoke when the JavaScript function is +invoked. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. +- `[in] utf8name`: Null-terminated string to be used as the name of the function. +- `[in] data`: User-provided data context. This will be passed back into the +function when invoked later. + +Returns an instance of a `Napi::Function` object. + +### New + +Creates an instance of a `Napi::Function` object. + +```cpp +template +static Napi::Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); +``` + +- `[template] cb`: The native function to invoke when the JavaScript function is +invoked. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. +- `[in] utf8name`: Null-terminated string to be used as the name of the function. +- `[in] data`: User-provided data context. This will be passed back into the +function when invoked later. + +Returns an instance of a `Napi::Function` object. + +### New + +Creates an instance of a `Napi::Function` object. + +```cpp +template +static Napi::Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); +``` + +- `[template] cb`: The native function to invoke when the JavaScript function is +invoked. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. +- `[in] utf8name`: String to be used as the name of the function. +- `[in] data`: User-provided data context. This will be passed back into the +function when invoked later. + +Returns an instance of a `Napi::Function` object. + +### New + +Creates an instance of a `Napi::Function` object. + +```cpp +template +static Napi::Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); +``` + +- `[template] cb`: The native function to invoke when the JavaScript function is +invoked. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. +- `[in] utf8name`: String to be used as the name of the function. +- `[in] data`: User-provided data context. This will be passed back into the +function when invoked later. + +Returns an instance of a `Napi::Function` object. + +### New + +Creates an instance of a `Napi::Function` object. + ```cpp template static Napi::Function Napi::Function::New(napi_env env, Callable cb, const char* utf8name = nullptr, void* data = nullptr); diff --git a/napi-inl.h b/napi-inl.h index f72e1daea..fa1ac6e5b 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1766,6 +1766,51 @@ CreateFunction(napi_env env, return status; } +template +inline Function Function::New(napi_env env, const char* utf8name, void* data) { + napi_value result = nullptr; + napi_status status = napi_create_function( + env, utf8name, NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) { + CallbackInfo callbackInfo(env, info); + return details::WrapCallback([&] { + cb(callbackInfo); + return nullptr; + }); + }, data, &result); + NAPI_THROW_IF_FAILED(env, status, Function()); + return Function(env, result); +} + +template +inline Function Function::New(napi_env env, const char* utf8name, void* data) { + napi_value result = nullptr; + napi_status status = napi_create_function( + env, utf8name, NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) { + CallbackInfo callbackInfo(env, info); + return details::WrapCallback([&] { + return cb(callbackInfo); + }); + }, data, &result); + NAPI_THROW_IF_FAILED(env, status, Function()); + return Function(env, result); +} + +template +inline Function Function::New(napi_env env, + const std::string& utf8name, + void* data) { + return Function::New(env, utf8name.c_str(), data); +} + +template +inline Function Function::New(napi_env env, + const std::string& utf8name, + void* data) { + return Function::New(env, utf8name.c_str(), data); +} + template inline Function Function::New(napi_env env, Callable cb, diff --git a/napi.h b/napi.h index 2fc9b10f3..6f0ae7ed5 100644 --- a/napi.h +++ b/napi.h @@ -991,8 +991,31 @@ namespace Napi { size_t _length; }; + class Function : public Object { public: + typedef void (*VoidCallback)(const CallbackInfo& info); + typedef Value (*Callback)(const CallbackInfo& info); + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + /// Callable must implement operator() accepting a const CallbackInfo& /// and return either void or Value. template diff --git a/test/function.cc b/test/function.cc index 8441e0b5f..b0ae92c7d 100644 --- a/test/function.cc +++ b/test/function.cc @@ -105,6 +105,7 @@ void IsConstructCall(const CallbackInfo& info) { } // end anonymous namespace Object InitFunction(Env env) { + Object result = Object::New(env); Object exports = Object::New(env); exports["voidCallback"] = Function::New(env, VoidCallback, "voidCallback"); exports["valueCallback"] = Function::New(env, ValueCallback, std::string("valueCallback")); @@ -120,5 +121,29 @@ Object InitFunction(Env env) { exports["callConstructorWithArgs"] = Function::New(env, CallConstructorWithArgs); exports["callConstructorWithVector"] = Function::New(env, CallConstructorWithVector); exports["isConstructCall"] = Function::New(env, IsConstructCall); - return exports; + result["plain"] = exports; + + exports = Object::New(env); + exports["voidCallback"] = Function::New(env, "voidCallback"); + exports["valueCallback"] = + Function::New(env, std::string("valueCallback")); + exports["voidCallbackWithData"] = + Function::New(env, nullptr, &testData); + exports["valueCallbackWithData"] = + Function::New(env, nullptr, &testData); + exports["callWithArgs"] = Function::New(env); + exports["callWithVector"] = Function::New(env); + exports["callWithReceiverAndArgs"] = + Function::New(env); + exports["callWithReceiverAndVector"] = + Function::New(env); + exports["callWithInvalidReceiver"] = + Function::New(env); + exports["callConstructorWithArgs"] = + Function::New(env); + exports["callConstructorWithVector"] = + Function::New(env); + exports["isConstructCall"] = Function::New(env); + result["templated"] = exports; + return result; } diff --git a/test/function.js b/test/function.js index 0d75ffca9..8ab742c27 100644 --- a/test/function.js +++ b/test/function.js @@ -2,15 +2,17 @@ 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`)); +test(require(`./build/${buildType}/binding.node`).function.plain); +test(require(`./build/${buildType}/binding_noexcept.node`).function.plain); +test(require(`./build/${buildType}/binding.node`).function.templated); +test(require(`./build/${buildType}/binding_noexcept.node`).function.templated); function test(binding) { let obj = {}; - assert.deepStrictEqual(binding.function.voidCallback(obj), undefined); + assert.deepStrictEqual(binding.voidCallback(obj), undefined); assert.deepStrictEqual(obj, { "foo": "bar" }); - assert.deepStrictEqual(binding.function.valueCallback(), { "foo": "bar" }); + assert.deepStrictEqual(binding.valueCallback(), { "foo": "bar" }); let args = null; let ret = null; @@ -25,50 +27,50 @@ function test(binding) { } ret = 4; - assert.equal(binding.function.callWithArgs(testFunction, 1, 2, 3), 4); + assert.equal(binding.callWithArgs(testFunction, 1, 2, 3), 4); assert.strictEqual(receiver, undefined); assert.deepStrictEqual(args, [ 1, 2, 3 ]); ret = 5; - assert.equal(binding.function.callWithVector(testFunction, 2, 3, 4), 5); + assert.equal(binding.callWithVector(testFunction, 2, 3, 4), 5); assert.strictEqual(receiver, undefined); assert.deepStrictEqual(args, [ 2, 3, 4 ]); ret = 6; - assert.equal(binding.function.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6); + assert.equal(binding.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6); assert.deepStrictEqual(receiver, obj); assert.deepStrictEqual(args, [ 3, 4, 5 ]); ret = 7; - assert.equal(binding.function.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7); + assert.equal(binding.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7); assert.deepStrictEqual(receiver, obj); assert.deepStrictEqual(args, [ 4, 5, 6 ]); assert.throws(() => { - binding.function.callWithInvalidReceiver(); + binding.callWithInvalidReceiver(); }, /Invalid (pointer passed as )?argument/); - obj = binding.function.callConstructorWithArgs(testConstructor, 5, 6, 7); + obj = binding.callConstructorWithArgs(testConstructor, 5, 6, 7); assert(obj instanceof testConstructor); assert.deepStrictEqual(args, [ 5, 6, 7 ]); - obj = binding.function.callConstructorWithVector(testConstructor, 6, 7, 8); + obj = binding.callConstructorWithVector(testConstructor, 6, 7, 8); assert(obj instanceof testConstructor); assert.deepStrictEqual(args, [ 6, 7, 8 ]); obj = {}; - assert.deepStrictEqual(binding.function.voidCallbackWithData(obj), undefined); + assert.deepStrictEqual(binding.voidCallbackWithData(obj), undefined); assert.deepStrictEqual(obj, { "foo": "bar", "data": 1 }); - assert.deepStrictEqual(binding.function.valueCallbackWithData(), { "foo": "bar", "data": 1 }); + assert.deepStrictEqual(binding.valueCallbackWithData(), { "foo": "bar", "data": 1 }); - assert.equal(binding.function.voidCallback.name, 'voidCallback'); - assert.equal(binding.function.valueCallback.name, 'valueCallback'); + assert.equal(binding.voidCallback.name, 'voidCallback'); + assert.equal(binding.valueCallback.name, 'valueCallback'); let testConstructCall = undefined; - binding.function.isConstructCall((result) => { testConstructCall = result; }); + binding.isConstructCall((result) => { testConstructCall = result; }); assert.ok(!testConstructCall); - new binding.function.isConstructCall((result) => { testConstructCall = result; }); + new binding.isConstructCall((result) => { testConstructCall = result; }); assert.ok(testConstructCall); // TODO: Function::MakeCallback tests