Skip to content

Commit 25a995e

Browse files
committed
implement Object::AddFinalizer
This allows one to tie the life cycle of one JavaScript object to another. In effect, this allows for JavaScript-style closures. Fixes: nodejs/node-addon-api#508 PR_URL: nodejs/node-addon-api#551 Reviewed-By: Kevin Eady <[email protected]>
1 parent bfad5c2 commit 25a995e

9 files changed

+156
-6
lines changed

doc/object.md

+34
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,40 @@ Returns a `bool` that is true if the `Napi::Object` is an instance created by th
137137

138138
Note: This is equivalent to the JavaScript instanceof operator.
139139

140+
### AddFinalizer()
141+
```cpp
142+
template <typename Finalizer, typename T>
143+
inline void AddFinalizer(Finalizer finalizeCallback, T* data);
144+
```
145+
146+
- `[in] finalizeCallback`: The function to call when the object is garbage-collected.
147+
- `[in] data`: The data to associate with the object.
148+
149+
Associates `data` with the object, calling `finalizeCallback` when the object is garbage-collected. `finalizeCallback`
150+
has the signature
151+
```cpp
152+
void finalizeCallback(Napi::Env env, T* data);
153+
```
154+
where `data` is the pointer that was passed into the call to `AddFinalizer()`.
155+
156+
### AddFinalizer()
157+
```cpp
158+
template <typename Finalizer, typename T, typename Hint>
159+
inline void AddFinalizer(Finalizer finalizeCallback,
160+
T* data,
161+
Hint* finalizeHint);
162+
```
163+
164+
- `[in] data`: The data to associate with the object.
165+
- `[in] finalizeCallback`: The function to call when the object is garbage-collected.
166+
167+
Associates `data` with the object, calling `finalizeCallback` when the object is garbage-collected. An additional hint
168+
may be given. It will also be passed to `finalizeCallback`, which has the signature
169+
```cpp
170+
void finalizeCallback(Napi::Env env, T* data, Hint* hint);
171+
```
172+
where `data` and `hint` are the pointers that were passed into the call to `AddFinalizer()`.
173+
140174
### DefineProperty()
141175

142176
```cpp

napi-inl.h

+44-5
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,21 @@ namespace details {
2424
template <typename FreeType>
2525
static inline napi_status AttachData(napi_env env,
2626
napi_value obj,
27-
FreeType* data) {
27+
FreeType* data,
28+
napi_finalize finalizer = nullptr,
29+
void* hint = nullptr) {
2830
napi_value symbol, external;
2931
napi_status status = napi_create_symbol(env, nullptr, &symbol);
3032
if (status == napi_ok) {
33+
if (finalizer == nullptr) {
34+
finalizer = [](napi_env /*env*/, void* data, void* /*hint*/) {
35+
delete static_cast<FreeType*>(data);
36+
};
37+
}
3138
status = napi_create_external(env,
3239
data,
33-
[](napi_env /*env*/, void* data, void* /*hint*/) {
34-
delete static_cast<FreeType*>(data);
35-
},
36-
nullptr,
40+
finalizer,
41+
hint,
3742
&external);
3843
if (status == napi_ok) {
3944
napi_property_descriptor desc = {
@@ -1170,6 +1175,40 @@ inline bool Object::InstanceOf(const Function& constructor) const {
11701175
return result;
11711176
}
11721177

1178+
template <typename Finalizer, typename T>
1179+
inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) {
1180+
details::FinalizeData<T, Finalizer>* finalizeData =
1181+
new details::FinalizeData<T, Finalizer>({ finalizeCallback, nullptr });
1182+
napi_status status =
1183+
details::AttachData(_env,
1184+
*this,
1185+
data,
1186+
details::FinalizeData<T, Finalizer>::Wrapper,
1187+
finalizeData);
1188+
if (status != napi_ok) {
1189+
delete finalizeData;
1190+
NAPI_THROW_IF_FAILED_VOID(_env, status);
1191+
}
1192+
}
1193+
1194+
template <typename Finalizer, typename T, typename Hint>
1195+
inline void Object::AddFinalizer(Finalizer finalizeCallback,
1196+
T* data,
1197+
Hint* finalizeHint) {
1198+
details::FinalizeData<T, Finalizer, Hint>* finalizeData =
1199+
new details::FinalizeData<T, Finalizer, Hint>({ finalizeCallback, finalizeHint });
1200+
napi_status status =
1201+
details::AttachData(_env,
1202+
*this,
1203+
data,
1204+
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint,
1205+
finalizeData);
1206+
if (status != napi_ok) {
1207+
delete finalizeData;
1208+
NAPI_THROW_IF_FAILED_VOID(_env, status);
1209+
}
1210+
}
1211+
11731212
////////////////////////////////////////////////////////////////////////////////
11741213
// External class
11751214
////////////////////////////////////////////////////////////////////////////////

napi.h

+8
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,14 @@ namespace Napi {
709709
bool InstanceOf(
710710
const Function& constructor ///< Constructor function
711711
) const;
712+
713+
template <typename Finalizer, typename T>
714+
inline void AddFinalizer(Finalizer finalizeCallback, T* data);
715+
716+
template <typename Finalizer, typename T, typename Hint>
717+
inline void AddFinalizer(Finalizer finalizeCallback,
718+
T* data,
719+
Hint* finalizeHint);
712720
};
713721

714722
template <typename T>

package.json

+11-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,17 @@
5555
},
5656
"directories": {},
5757
"homepage": "https://github.com/nodejs/node-addon-api",
58-
"keywords": ["n-api", "napi", "addon", "native", "bindings", "c", "c++", "nan", "node-addon-api"],
58+
"keywords": [
59+
"n-api",
60+
"napi",
61+
"addon",
62+
"native",
63+
"bindings",
64+
"c",
65+
"c++",
66+
"nan",
67+
"node-addon-api"
68+
],
5969
"license": "MIT",
6070
"main": "index.js",
6171
"name": "node-addon-api",

test/binding.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
'memory_management.cc',
2828
'name.cc',
2929
'object/delete_property.cc',
30+
'object/finalizer.cc',
3031
'object/get_property.cc',
3132
'object/has_own_property.cc',
3233
'object/has_property.cc',

test/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ let testModules = [
3030
'memory_management',
3131
'name',
3232
'object/delete_property',
33+
'object/finalizer',
3334
'object/get_property',
3435
'object/has_own_property',
3536
'object/has_property',

test/object/finalizer.cc

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#include "napi.h"
2+
3+
using namespace Napi;
4+
5+
static int dummy;
6+
7+
Value AddFinalizer(const CallbackInfo& info) {
8+
ObjectReference* ref = new ObjectReference;
9+
*ref = Persistent(Object::New(info.Env()));
10+
info[0]
11+
.As<Object>()
12+
.AddFinalizer([](Napi::Env /*env*/, ObjectReference* ref) {
13+
ref->Set("finalizerCalled", true);
14+
delete ref;
15+
}, ref);
16+
return ref->Value();
17+
}
18+
19+
Value AddFinalizerWithHint(const CallbackInfo& info) {
20+
ObjectReference* ref = new ObjectReference;
21+
*ref = Persistent(Object::New(info.Env()));
22+
info[0]
23+
.As<Object>()
24+
.AddFinalizer([](Napi::Env /*env*/, ObjectReference* ref, int* dummy_p) {
25+
ref->Set("finalizerCalledWithCorrectHint", dummy_p == &dummy);
26+
delete ref;
27+
}, ref, &dummy);
28+
return ref->Value();
29+
}

test/object/finalizer.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
const buildType = process.config.target_defaults.default_configuration;
4+
const assert = require('assert');
5+
6+
test(require(`../build/${buildType}/binding.node`));
7+
test(require(`../build/${buildType}/binding_noexcept.node`));
8+
9+
function createWeakRef(binding, bindingToTest) {
10+
return binding.object[bindingToTest]({});
11+
}
12+
13+
function test(binding) {
14+
const obj1 = createWeakRef(binding, 'addFinalizer');
15+
global.gc();
16+
assert.deepStrictEqual(obj1, { finalizerCalled: true });
17+
18+
const obj2 = createWeakRef(binding, 'addFinalizerWithHint');
19+
global.gc();
20+
assert.deepStrictEqual(obj2, { finalizerCalledWithCorrectHint: true });
21+
}

test/object/object.cc

+7
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ Value HasPropertyWithNapiWrapperValue(const CallbackInfo& info);
3232
Value HasPropertyWithCStyleString(const CallbackInfo& info);
3333
Value HasPropertyWithCppStyleString(const CallbackInfo& info);
3434

35+
// Native wrappers for testing Object::AddFinalizer()
36+
Value AddFinalizer(const CallbackInfo& info);
37+
Value AddFinalizerWithHint(const CallbackInfo& info);
38+
3539
static bool testValue = true;
3640
// Used to test void* Data() integrity
3741
struct UserDataHolder {
@@ -201,5 +205,8 @@ Object InitObject(Env env) {
201205

202206
exports["createObjectUsingMagic"] = Function::New(env, CreateObjectUsingMagic);
203207

208+
exports["addFinalizer"] = Function::New(env, AddFinalizer);
209+
exports["addFinalizerWithHint"] = Function::New(env, AddFinalizerWithHint);
210+
204211
return exports;
205212
}

0 commit comments

Comments
 (0)