Skip to content

Commit a1b1060

Browse files
author
Gabriel Schulhof
committed
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. PR-URL: #608 Reviewed-By: Nicola Del Gobbo <nicoladelgobbo@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
1 parent c584343 commit a1b1060

File tree

5 files changed

+215
-19
lines changed

5 files changed

+215
-19
lines changed

doc/function.md

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Value Fn(const CallbackInfo& info) {
2525
}
2626

2727
Object Init(Env env, Object exports) {
28-
exports.Set(String::New(env, "fn"), Function::New(env, Fn));
28+
exports.Set(String::New(env, "fn"), Function::New<Fn>(env));
2929
}
3030

3131
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
4747
the stack. The `Call` method is used when there is already a JavaScript function
4848
on the stack (for example when running a native method called from JavaScript).
4949

50+
## Type definitions
51+
52+
### Napi::Function::VoidCallback
53+
54+
This is the type describing a callback returning `void` that will be invoked
55+
from JavaScript.
56+
57+
```cpp
58+
typedef void (*VoidCallback)(const Napi::CallbackInfo& info);
59+
```
60+
61+
### Napi::Function::Callback
62+
63+
This is the type describing a callback returning a value that will be invoked
64+
from JavaScript.
65+
66+
67+
```cpp
68+
typedef Value (*Callback)(const Napi::CallbackInfo& info);
69+
```
70+
5071
## Methods
5172

5273
### Constructor
@@ -74,6 +95,86 @@ Returns a non-empty `Napi::Function` instance.
7495
7596
Creates an instance of a `Napi::Function` object.
7697
98+
```cpp
99+
template <Napi::VoidCallback cb>
100+
static Napi::Function New(napi_env env,
101+
const char* utf8name = nullptr,
102+
void* data = nullptr);
103+
```
104+
105+
- `[template] cb`: The native function to invoke when the JavaScript function is
106+
invoked.
107+
- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object.
108+
- `[in] utf8name`: Null-terminated string to be used as the name of the function.
109+
- `[in] data`: User-provided data context. This will be passed back into the
110+
function when invoked later.
111+
112+
Returns an instance of a `Napi::Function` object.
113+
114+
### New
115+
116+
Creates an instance of a `Napi::Function` object.
117+
118+
```cpp
119+
template <Napi::Callback cb>
120+
static Napi::Function New(napi_env env,
121+
const char* utf8name = nullptr,
122+
void* data = nullptr);
123+
```
124+
125+
- `[template] cb`: The native function to invoke when the JavaScript function is
126+
invoked.
127+
- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object.
128+
- `[in] utf8name`: Null-terminated string to be used as the name of the function.
129+
- `[in] data`: User-provided data context. This will be passed back into the
130+
function when invoked later.
131+
132+
Returns an instance of a `Napi::Function` object.
133+
134+
### New
135+
136+
Creates an instance of a `Napi::Function` object.
137+
138+
```cpp
139+
template <Napi::VoidCallback cb>
140+
static Napi::Function New(napi_env env,
141+
const std::string& utf8name,
142+
void* data = nullptr);
143+
```
144+
145+
- `[template] cb`: The native function to invoke when the JavaScript function is
146+
invoked.
147+
- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object.
148+
- `[in] utf8name`: String to be used as the name of the function.
149+
- `[in] data`: User-provided data context. This will be passed back into the
150+
function when invoked later.
151+
152+
Returns an instance of a `Napi::Function` object.
153+
154+
### New
155+
156+
Creates an instance of a `Napi::Function` object.
157+
158+
```cpp
159+
template <Napi::Callback cb>
160+
static Napi::Function New(napi_env env,
161+
const std::string& utf8name,
162+
void* data = nullptr);
163+
```
164+
165+
- `[template] cb`: The native function to invoke when the JavaScript function is
166+
invoked.
167+
- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object.
168+
- `[in] utf8name`: String to be used as the name of the function.
169+
- `[in] data`: User-provided data context. This will be passed back into the
170+
function when invoked later.
171+
172+
Returns an instance of a `Napi::Function` object.
173+
174+
### New
175+
176+
Creates an instance of a `Napi::Function` object.
177+
77178
```cpp
78179
template <typename Callable>
79180
static Napi::Function Napi::Function::New(napi_env env, Callable cb, const char* utf8name = nullptr, void* data = nullptr);

napi-inl.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,51 @@ CreateFunction(napi_env env,
17661766
return status;
17671767
}
17681768

1769+
template <Function::VoidCallback cb>
1770+
inline Function Function::New(napi_env env, const char* utf8name, void* data) {
1771+
napi_value result = nullptr;
1772+
napi_status status = napi_create_function(
1773+
env, utf8name, NAPI_AUTO_LENGTH,
1774+
[](napi_env env, napi_callback_info info) {
1775+
CallbackInfo callbackInfo(env, info);
1776+
return details::WrapCallback([&] {
1777+
cb(callbackInfo);
1778+
return nullptr;
1779+
});
1780+
}, data, &result);
1781+
NAPI_THROW_IF_FAILED(env, status, Function());
1782+
return Function(env, result);
1783+
}
1784+
1785+
template <Function::Callback cb>
1786+
inline Function Function::New(napi_env env, const char* utf8name, void* data) {
1787+
napi_value result = nullptr;
1788+
napi_status status = napi_create_function(
1789+
env, utf8name, NAPI_AUTO_LENGTH,
1790+
[](napi_env env, napi_callback_info info) {
1791+
CallbackInfo callbackInfo(env, info);
1792+
return details::WrapCallback([&] {
1793+
return cb(callbackInfo);
1794+
});
1795+
}, data, &result);
1796+
NAPI_THROW_IF_FAILED(env, status, Function());
1797+
return Function(env, result);
1798+
}
1799+
1800+
template <Function::VoidCallback cb>
1801+
inline Function Function::New(napi_env env,
1802+
const std::string& utf8name,
1803+
void* data) {
1804+
return Function::New<cb>(env, utf8name.c_str(), data);
1805+
}
1806+
1807+
template <Function::Callback cb>
1808+
inline Function Function::New(napi_env env,
1809+
const std::string& utf8name,
1810+
void* data) {
1811+
return Function::New<cb>(env, utf8name.c_str(), data);
1812+
}
1813+
17691814
template <typename Callable>
17701815
inline Function Function::New(napi_env env,
17711816
Callable cb,

napi.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,29 @@ namespace Napi {
993993

994994
class Function : public Object {
995995
public:
996+
typedef void (*VoidCallback)(const CallbackInfo& info);
997+
typedef Value (*Callback)(const CallbackInfo& info);
998+
999+
template <VoidCallback cb>
1000+
static Function New(napi_env env,
1001+
const char* utf8name = nullptr,
1002+
void* data = nullptr);
1003+
1004+
template <Callback cb>
1005+
static Function New(napi_env env,
1006+
const char* utf8name = nullptr,
1007+
void* data = nullptr);
1008+
1009+
template <VoidCallback cb>
1010+
static Function New(napi_env env,
1011+
const std::string& utf8name,
1012+
void* data = nullptr);
1013+
1014+
template <Callback cb>
1015+
static Function New(napi_env env,
1016+
const std::string& utf8name,
1017+
void* data = nullptr);
1018+
9961019
/// Callable must implement operator() accepting a const CallbackInfo&
9971020
/// and return either void or Value.
9981021
template <typename Callable>

test/function.cc

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ void IsConstructCall(const CallbackInfo& info) {
105105
} // end anonymous namespace
106106

107107
Object InitFunction(Env env) {
108+
Object result = Object::New(env);
108109
Object exports = Object::New(env);
109110
exports["voidCallback"] = Function::New(env, VoidCallback, "voidCallback");
110111
exports["valueCallback"] = Function::New(env, ValueCallback, std::string("valueCallback"));
@@ -120,5 +121,29 @@ Object InitFunction(Env env) {
120121
exports["callConstructorWithArgs"] = Function::New(env, CallConstructorWithArgs);
121122
exports["callConstructorWithVector"] = Function::New(env, CallConstructorWithVector);
122123
exports["isConstructCall"] = Function::New(env, IsConstructCall);
123-
return exports;
124+
result["plain"] = exports;
125+
126+
exports = Object::New(env);
127+
exports["voidCallback"] = Function::New<VoidCallback>(env, "voidCallback");
128+
exports["valueCallback"] =
129+
Function::New<ValueCallback>(env, std::string("valueCallback"));
130+
exports["voidCallbackWithData"] =
131+
Function::New<VoidCallbackWithData>(env, nullptr, &testData);
132+
exports["valueCallbackWithData"] =
133+
Function::New<ValueCallbackWithData>(env, nullptr, &testData);
134+
exports["callWithArgs"] = Function::New<CallWithArgs>(env);
135+
exports["callWithVector"] = Function::New<CallWithVector>(env);
136+
exports["callWithReceiverAndArgs"] =
137+
Function::New<CallWithReceiverAndArgs>(env);
138+
exports["callWithReceiverAndVector"] =
139+
Function::New<CallWithReceiverAndVector>(env);
140+
exports["callWithInvalidReceiver"] =
141+
Function::New<CallWithInvalidReceiver>(env);
142+
exports["callConstructorWithArgs"] =
143+
Function::New<CallConstructorWithArgs>(env);
144+
exports["callConstructorWithVector"] =
145+
Function::New<CallConstructorWithVector>(env);
146+
exports["isConstructCall"] = Function::New<IsConstructCall>(env);
147+
result["templated"] = exports;
148+
return result;
124149
}

test/function.js

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
const buildType = process.config.target_defaults.default_configuration;
33
const assert = require('assert');
44

5-
test(require(`./build/${buildType}/binding.node`));
6-
test(require(`./build/${buildType}/binding_noexcept.node`));
5+
test(require(`./build/${buildType}/binding.node`).function.plain);
6+
test(require(`./build/${buildType}/binding_noexcept.node`).function.plain);
7+
test(require(`./build/${buildType}/binding.node`).function.templated);
8+
test(require(`./build/${buildType}/binding_noexcept.node`).function.templated);
79

810
function test(binding) {
911
let obj = {};
10-
assert.deepStrictEqual(binding.function.voidCallback(obj), undefined);
12+
assert.deepStrictEqual(binding.voidCallback(obj), undefined);
1113
assert.deepStrictEqual(obj, { "foo": "bar" });
1214

13-
assert.deepStrictEqual(binding.function.valueCallback(), { "foo": "bar" });
15+
assert.deepStrictEqual(binding.valueCallback(), { "foo": "bar" });
1416

1517
let args = null;
1618
let ret = null;
@@ -25,50 +27,50 @@ function test(binding) {
2527
}
2628

2729
ret = 4;
28-
assert.equal(binding.function.callWithArgs(testFunction, 1, 2, 3), 4);
30+
assert.equal(binding.callWithArgs(testFunction, 1, 2, 3), 4);
2931
assert.strictEqual(receiver, undefined);
3032
assert.deepStrictEqual(args, [ 1, 2, 3 ]);
3133

3234
ret = 5;
33-
assert.equal(binding.function.callWithVector(testFunction, 2, 3, 4), 5);
35+
assert.equal(binding.callWithVector(testFunction, 2, 3, 4), 5);
3436
assert.strictEqual(receiver, undefined);
3537
assert.deepStrictEqual(args, [ 2, 3, 4 ]);
3638

3739
ret = 6;
38-
assert.equal(binding.function.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6);
40+
assert.equal(binding.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6);
3941
assert.deepStrictEqual(receiver, obj);
4042
assert.deepStrictEqual(args, [ 3, 4, 5 ]);
4143

4244
ret = 7;
43-
assert.equal(binding.function.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7);
45+
assert.equal(binding.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7);
4446
assert.deepStrictEqual(receiver, obj);
4547
assert.deepStrictEqual(args, [ 4, 5, 6 ]);
4648

4749
assert.throws(() => {
48-
binding.function.callWithInvalidReceiver();
50+
binding.callWithInvalidReceiver();
4951
}, /Invalid (pointer passed as )?argument/);
5052

51-
obj = binding.function.callConstructorWithArgs(testConstructor, 5, 6, 7);
53+
obj = binding.callConstructorWithArgs(testConstructor, 5, 6, 7);
5254
assert(obj instanceof testConstructor);
5355
assert.deepStrictEqual(args, [ 5, 6, 7 ]);
5456

55-
obj = binding.function.callConstructorWithVector(testConstructor, 6, 7, 8);
57+
obj = binding.callConstructorWithVector(testConstructor, 6, 7, 8);
5658
assert(obj instanceof testConstructor);
5759
assert.deepStrictEqual(args, [ 6, 7, 8 ]);
5860

5961
obj = {};
60-
assert.deepStrictEqual(binding.function.voidCallbackWithData(obj), undefined);
62+
assert.deepStrictEqual(binding.voidCallbackWithData(obj), undefined);
6163
assert.deepStrictEqual(obj, { "foo": "bar", "data": 1 });
6264

63-
assert.deepStrictEqual(binding.function.valueCallbackWithData(), { "foo": "bar", "data": 1 });
65+
assert.deepStrictEqual(binding.valueCallbackWithData(), { "foo": "bar", "data": 1 });
6466

65-
assert.equal(binding.function.voidCallback.name, 'voidCallback');
66-
assert.equal(binding.function.valueCallback.name, 'valueCallback');
67+
assert.equal(binding.voidCallback.name, 'voidCallback');
68+
assert.equal(binding.valueCallback.name, 'valueCallback');
6769

6870
let testConstructCall = undefined;
69-
binding.function.isConstructCall((result) => { testConstructCall = result; });
71+
binding.isConstructCall((result) => { testConstructCall = result; });
7072
assert.ok(!testConstructCall);
71-
new binding.function.isConstructCall((result) => { testConstructCall = result; });
73+
new binding.isConstructCall((result) => { testConstructCall = result; });
7274
assert.ok(testConstructCall);
7375

7476
// TODO: Function::MakeCallback tests

0 commit comments

Comments
 (0)