Skip to content

Commit 9c9accf

Browse files
author
Gabriel Schulhof
committed
src: add support for addon instance data
Support `napi_get_instance_data()` and `napi_set_instance_data()`. Signed-off-by: Gabriel Schulhof <[email protected]> Fixes: #654 PR-URL: #663 Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Michael Dawson <[email protected]>
1 parent 82a9650 commit 9c9accf

File tree

7 files changed

+212
-0
lines changed

7 files changed

+212
-0
lines changed

napi-inl.h

+42
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,48 @@ inline Value Env::RunScript(String script) {
322322
return Value(_env, result);
323323
}
324324

325+
#if NAPI_VERSION > 5
326+
template <typename T, Env::Finalizer<T> fini>
327+
inline void Env::SetInstanceData(T* data) {
328+
napi_status status =
329+
napi_set_instance_data(_env, data, [](napi_env env, void* data, void*) {
330+
fini(env, static_cast<T*>(data));
331+
}, nullptr);
332+
NAPI_THROW_IF_FAILED_VOID(_env, status);
333+
}
334+
335+
template <typename DataType,
336+
typename HintType,
337+
Napi::Env::FinalizerWithHint<DataType, HintType> fini>
338+
inline void Env::SetInstanceData(DataType* data, HintType* hint) {
339+
napi_status status =
340+
napi_set_instance_data(_env, data,
341+
[](napi_env env, void* data, void* hint) {
342+
fini(env, static_cast<DataType*>(data), static_cast<HintType*>(hint));
343+
}, hint);
344+
NAPI_THROW_IF_FAILED_VOID(_env, status);
345+
}
346+
347+
template <typename T>
348+
inline T* Env::GetInstanceData() {
349+
void* data = nullptr;
350+
351+
napi_status status = napi_get_instance_data(_env, &data);
352+
NAPI_THROW_IF_FAILED(_env, status, nullptr);
353+
354+
return static_cast<T*>(data);
355+
}
356+
357+
template <typename T> void Env::DefaultFini(Env, T* data) {
358+
delete data;
359+
}
360+
361+
template <typename DataType, typename HintType>
362+
void Env::DefaultFiniWithHint(Env, DataType* data, HintType*) {
363+
delete data;
364+
}
365+
#endif // NAPI_VERSION > 5
366+
325367
////////////////////////////////////////////////////////////////////////////////
326368
// Value class
327369
////////////////////////////////////////////////////////////////////////////////

napi.h

+22
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ namespace Napi {
166166
///
167167
/// In the V8 JavaScript engine, a N-API environment approximately corresponds to an Isolate.
168168
class Env {
169+
#if NAPI_VERSION > 5
170+
private:
171+
template <typename T> static void DefaultFini(Env, T* data);
172+
template <typename DataType, typename HintType>
173+
static void DefaultFiniWithHint(Env, DataType* data, HintType* hint);
174+
#endif // NAPI_VERSION > 5
169175
public:
170176
Env(napi_env env);
171177

@@ -182,6 +188,22 @@ namespace Napi {
182188
Value RunScript(const std::string& utf8script);
183189
Value RunScript(String script);
184190

191+
#if NAPI_VERSION > 5
192+
template <typename T> T* GetInstanceData();
193+
194+
template <typename T> using Finalizer = void (*)(Env, T*);
195+
template <typename T, Finalizer<T> fini = Env::DefaultFini<T>>
196+
void SetInstanceData(T* data);
197+
198+
template <typename DataType, typename HintType>
199+
using FinalizerWithHint = void (*)(Env, DataType*, HintType*);
200+
template <typename DataType,
201+
typename HintType,
202+
FinalizerWithHint<DataType, HintType> fini =
203+
Env::DefaultFiniWithHint<DataType, HintType>>
204+
void SetInstanceData(DataType* data, HintType* hint);
205+
#endif // NAPI_VERSION > 5
206+
185207
private:
186208
napi_env _env;
187209
};

test/addon_data.cc

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#if (NAPI_VERSION > 5)
2+
#include <stdio.h>
3+
#include "napi.h"
4+
5+
// An overly elaborate way to get/set a boolean stored in the instance data:
6+
// 0. A boolean named "verbose" is stored in the instance data. The constructor
7+
// for JS `VerboseIndicator` instances is also stored in the instance data.
8+
// 1. Add a property named "verbose" onto exports served by a getter/setter.
9+
// 2. The getter returns a object of type VerboseIndicator, which itself has a
10+
// property named "verbose", also served by a getter/setter:
11+
// * The getter returns a boolean, indicating whether "verbose" is set.
12+
// * The setter sets "verbose" on the instance data.
13+
// 3. The setter sets "verbose" on the instance data.
14+
15+
class Addon {
16+
public:
17+
class VerboseIndicator : public Napi::ObjectWrap<VerboseIndicator> {
18+
public:
19+
VerboseIndicator(const Napi::CallbackInfo& info):
20+
Napi::ObjectWrap<VerboseIndicator>(info) {
21+
info.This().As<Napi::Object>()["verbose"] =
22+
Napi::Boolean::New(info.Env(),
23+
info.Env().GetInstanceData<Addon>()->verbose);
24+
}
25+
26+
Napi::Value Getter(const Napi::CallbackInfo& info) {
27+
return Napi::Boolean::New(info.Env(),
28+
info.Env().GetInstanceData<Addon>()->verbose);
29+
}
30+
31+
void Setter(const Napi::CallbackInfo& info, const Napi::Value& val) {
32+
info.Env().GetInstanceData<Addon>()->verbose = val.As<Napi::Boolean>();
33+
}
34+
35+
static Napi::FunctionReference Init(Napi::Env env) {
36+
return Napi::Persistent(DefineClass(env, "VerboseIndicator", {
37+
InstanceAccessor<
38+
&VerboseIndicator::Getter,
39+
&VerboseIndicator::Setter>("verbose")
40+
}));
41+
}
42+
};
43+
44+
static Napi::Value Getter(const Napi::CallbackInfo& info) {
45+
return info.Env().GetInstanceData<Addon>()->VerboseIndicator.New({});
46+
}
47+
48+
static void Setter(const Napi::CallbackInfo& info) {
49+
info.Env().GetInstanceData<Addon>()->verbose = info[0].As<Napi::Boolean>();
50+
}
51+
52+
Addon(Napi::Env env): VerboseIndicator(VerboseIndicator::Init(env)) {}
53+
~Addon() {
54+
if (verbose) {
55+
fprintf(stderr, "addon_data: Addon::~Addon\n");
56+
}
57+
}
58+
59+
static void DeleteAddon(Napi::Env, Addon* addon, uint32_t* hint) {
60+
delete addon;
61+
fprintf(stderr, "hint: %d\n", *hint);
62+
delete hint;
63+
}
64+
65+
static Napi::Object Init(Napi::Env env, Napi::Value jshint) {
66+
if (!jshint.IsNumber()) {
67+
NAPI_THROW(Napi::Error::New(env, "Expected number"), Napi::Object());
68+
}
69+
uint32_t hint = jshint.As<Napi::Number>();
70+
if (hint == 0)
71+
env.SetInstanceData(new Addon(env));
72+
else
73+
env.SetInstanceData<Addon, uint32_t, DeleteAddon>(new Addon(env),
74+
new uint32_t(hint));
75+
Napi::Object result = Napi::Object::New(env);
76+
result.DefineProperties({
77+
Napi::PropertyDescriptor::Accessor<Getter, Setter>("verbose"),
78+
});
79+
80+
return result;
81+
}
82+
83+
private:
84+
bool verbose = false;
85+
Napi::FunctionReference VerboseIndicator;
86+
};
87+
88+
// We use an addon factory so we can cover both the case where there is an
89+
// instance data hint and the case where there isn't.
90+
static Napi::Value AddonFactory(const Napi::CallbackInfo& info) {
91+
return Addon::Init(info.Env(), info[0]);
92+
}
93+
94+
Napi::Object InitAddonData(Napi::Env env) {
95+
return Napi::Function::New(env, AddonFactory);
96+
}
97+
#endif // (NAPI_VERSION > 5)

test/addon_data.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
const buildType = process.config.target_defaults.default_configuration;
3+
const assert = require('assert');
4+
const { spawn } = require('child_process');
5+
const readline = require('readline');
6+
const path = require('path');
7+
8+
test(path.resolve(__dirname, `./build/${buildType}/binding.node`));
9+
test(path.resolve(__dirname, `./build/${buildType}/binding_noexcept.node`));
10+
11+
// Make sure the instance data finalizer is called at process exit. If the hint
12+
// is non-zero, it will be printed out by the child process.
13+
function testFinalizer(bindingName, hint, expected) {
14+
bindingName = bindingName.split('\\').join('\\\\');
15+
const child = spawn(process.execPath, [
16+
'-e',
17+
`require('${bindingName}').addon_data(${hint}).verbose = true;`
18+
]);
19+
const actual = [];
20+
readline
21+
.createInterface({ input: child.stderr })
22+
.on('line', (line) => {
23+
if (expected.indexOf(line) >= 0) {
24+
actual.push(line);
25+
}
26+
})
27+
.on('close', () => assert.deepStrictEqual(expected, actual));
28+
}
29+
30+
function test(bindingName) {
31+
const binding = require(bindingName).addon_data(0);
32+
33+
// Make sure it is possible to get/set instance data.
34+
assert.strictEqual(binding.verbose.verbose, false);
35+
binding.verbose = true;
36+
assert.strictEqual(binding.verbose.verbose, true);
37+
binding.verbose = false;
38+
assert.strictEqual(binding.verbose.verbose, false);
39+
40+
testFinalizer(bindingName, 0, ['addon_data: Addon::~Addon']);
41+
testFinalizer(bindingName, 42, ['addon_data: Addon::~Addon', 'hint: 42']);
42+
}

test/binding.cc

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
using namespace Napi;
44

5+
#if (NAPI_VERSION > 5)
6+
Object InitAddonData(Env env);
7+
#endif
58
Object InitArrayBuffer(Env env);
69
Object InitAsyncContext(Env env);
710
#if (NAPI_VERSION > 3)
@@ -55,6 +58,9 @@ Object InitVersionManagement(Env env);
5558
Object InitThunkingManual(Env env);
5659

5760
Object Init(Env env, Object exports) {
61+
#if (NAPI_VERSION > 5)
62+
exports.Set("addon_data", InitAddonData(env));
63+
#endif
5864
exports.Set("arraybuffer", InitArrayBuffer(env));
5965
exports.Set("asynccontext", InitAsyncContext(env));
6066
#if (NAPI_VERSION > 3)

test/binding.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
'target_defaults': {
33
'includes': ['../common.gypi'],
44
'sources': [
5+
'addon_data.cc',
56
'arraybuffer.cc',
67
'asynccontext.cc',
78
'asyncprogressqueueworker.cc',

test/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ process.config.target_defaults.default_configuration =
88
// FIXME: We might need a way to load test modules automatically without
99
// explicit declaration as follows.
1010
let testModules = [
11+
'addon_data',
1112
'arraybuffer',
1213
'asynccontext',
1314
'asyncprogressqueueworker',
@@ -81,6 +82,7 @@ if (napiVersion < 5) {
8182
if (napiVersion < 6) {
8283
testModules.splice(testModules.indexOf('bigint'), 1);
8384
testModules.splice(testModules.indexOf('typedarray-bigint'), 1);
85+
testModules.splice(testModules.indexOf('addon_data'), 1);
8486
}
8587

8688
if (typeof global.gc === 'function') {

0 commit comments

Comments
 (0)