Skip to content

Commit 7db12f7

Browse files
author
Gabriel Schulhof
committed
src: add support for addon instance data
Support `napi_get_instance_data()` and `napi_set_instance_data()`. Fixes: #654
1 parent e1a827a commit 7db12f7

File tree

7 files changed

+160
-0
lines changed

7 files changed

+160
-0
lines changed

napi-inl.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,31 @@ 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, void* data, void*) {
330+
fini(static_cast<T*>(data));
331+
}, nullptr);
332+
NAPI_THROW_IF_FAILED_VOID(_env, status);
333+
}
334+
335+
template <typename T>
336+
inline T* Env::GetInstanceData() {
337+
void* data = nullptr;
338+
339+
napi_status status = napi_get_instance_data(_env, &data);
340+
NAPI_THROW_IF_FAILED(_env, status, nullptr);
341+
342+
return static_cast<T*>(data);
343+
}
344+
345+
template <typename T> void Env::DefaultFini(T* data) {
346+
delete data;
347+
}
348+
#endif // NAPI_VERSION > 5
349+
325350
////////////////////////////////////////////////////////////////////////////////
326351
// Value class
327352
////////////////////////////////////////////////////////////////////////////////

napi.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ namespace Napi {
172172
///
173173
/// In the V8 JavaScript engine, a N-API environment approximately corresponds to an Isolate.
174174
class Env {
175+
#if NAPI_VERSION > 5
176+
private:
177+
template <typename T> static void DefaultFini(T* data);
178+
#endif // NAPI_VERSION > 5
175179
public:
176180
Env(napi_env env);
177181

@@ -188,6 +192,13 @@ namespace Napi {
188192
Value RunScript(const std::string& utf8script);
189193
Value RunScript(String script);
190194

195+
#if NAPI_VERSION > 5
196+
template <typename T> using Finalizer = void (*)(T*);
197+
template <typename T, Finalizer<T> fini = Env::DefaultFini<T>>
198+
void SetInstanceData(T* data);
199+
template <typename T> T* GetInstanceData();
200+
#endif // NAPI_VERSION > 5
201+
191202
private:
192203
napi_env _env;
193204
};

test/addon_data.cc

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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 Napi::Object Init(Napi::Env env) {
60+
env.SetInstanceData(new Addon(env));
61+
Napi::Object result = Napi::Object::New(env);
62+
result.DefineProperties({
63+
Napi::PropertyDescriptor::Accessor<Getter, Setter>("verbose"),
64+
});
65+
66+
return result;
67+
}
68+
69+
private:
70+
bool verbose = false;
71+
Napi::FunctionReference VerboseIndicator;
72+
};
73+
74+
Napi::Object InitAddonData(Napi::Env env) {
75+
return Addon::Init(env);
76+
}
77+
#endif // (NAPI_VERSION > 5)

test/addon_data.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
function test(bindingName) {
12+
const binding = require(bindingName);
13+
14+
// Make sure it is possible to get/set instance data.
15+
assert.strictEqual(binding.addon_data.verbose.verbose, false);
16+
binding.addon_data.verbose = true;
17+
assert.strictEqual(binding.addon_data.verbose.verbose, true);
18+
binding.addon_data.verbose = false;
19+
assert.strictEqual(binding.addon_data.verbose.verbose, false);
20+
21+
// Make sure the instance data finalizer is called at process exit.
22+
const child = spawn(process.execPath, [
23+
'-e',
24+
`require('${bindingName}').addon_data.verbose = true;`
25+
]);
26+
let foundMessage = false;
27+
readline
28+
.createInterface({ input: child.stderr })
29+
.on('line', (line) => {
30+
if (line.match('addon_data: Addon::~Addon')) {
31+
foundMessage = true;
32+
}
33+
})
34+
.on('close', () => assert.strictEqual(foundMessage, true));
35+
}

test/binding.cc

Lines changed: 6 additions & 0 deletions
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)
@@ -58,6 +61,9 @@ Object InitVersionManagement(Env env);
5861
Object InitThunkingManual(Env env);
5962

6063
Object Init(Env env, Object exports) {
64+
#if (NAPI_VERSION > 5)
65+
exports.Set("addon_data", InitAddonData(env));
66+
#endif
6167
exports.Set("arraybuffer", InitArrayBuffer(env));
6268
exports.Set("asynccontext", InitAsyncContext(env));
6369
#if (NAPI_VERSION > 3)

test/binding.gyp

Lines changed: 1 addition & 0 deletions
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

Lines changed: 5 additions & 0 deletions
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',
@@ -87,6 +88,10 @@ if (napiVersion < 5) {
8788
testModules.splice(testModules.indexOf('date'), 1);
8889
}
8990

91+
if (napiVersion < 6) {
92+
testModules.splice(testModules.indexOf('addon_data'), 1);
93+
}
94+
9095
if (typeof global.gc === 'function') {
9196
console.log(`Testing with N-API Version '${napiVersion}'.`);
9297
console.log(`Testing with Node.js Major Version '${nodeMajorVersion}'.\n`);

0 commit comments

Comments
 (0)