Skip to content

Commit b16c762

Browse files
legendecasmhdawson
authored andcommitted
src: handle no support for external buffers
PR-URL: #1273 Reviewed-By: Michael Dawson <[email protected]
1 parent 61b8e28 commit b16c762

File tree

12 files changed

+456
-20
lines changed

12 files changed

+456
-20
lines changed

doc/array_buffer.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ Returns a new `Napi::ArrayBuffer` instance.
2323
2424
### New
2525
26+
> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available.
27+
> See [External Buffer][] for more information.
28+
2629
Wraps the provided external data into a new `Napi::ArrayBuffer` instance.
2730
2831
The `Napi::ArrayBuffer` instance does not assume ownership for the data and
@@ -48,6 +51,9 @@ Returns a new `Napi::ArrayBuffer` instance.
4851

4952
### New
5053

54+
> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available.
55+
> See [External Buffer][] for more information.
56+
5157
Wraps the provided external data into a new `Napi::ArrayBuffer` instance.
5258

5359
The `Napi::ArrayBuffer` instance does not assume ownership for the data and
@@ -74,6 +80,9 @@ Returns a new `Napi::ArrayBuffer` instance.
7480
7581
### New
7682
83+
> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available.
84+
> See [External Buffer][] for more information.
85+
7786
Wraps the provided external data into a new `Napi::ArrayBuffer` instance.
7887
7988
The `Napi::ArrayBuffer` instance does not assume ownership for the data and expects it
@@ -153,3 +162,4 @@ bool Napi::ArrayBuffer::IsDetached() const;
153162
Returns `true` if this `ArrayBuffer` has been detached.
154163

155164
[`Napi::Object`]: ./object.md
165+
[External Buffer]: ./external_buffer.md

doc/buffer.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ Returns a new `Napi::Buffer` object.
2222
2323
### New
2424
25+
> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available.
26+
> See [External Buffer][] for more information.
27+
2528
Wraps the provided external data into a new `Napi::Buffer` object.
2629
2730
The `Napi::Buffer` object does not assume ownership for the data and expects it to be
@@ -47,6 +50,9 @@ Returns a new `Napi::Buffer` object.
4750

4851
### New
4952

53+
> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available.
54+
> See [External Buffer][] for more information.
55+
5056
Wraps the provided external data into a new `Napi::Buffer` object.
5157

5258
The `Napi::Buffer` object does not assume ownership for the data and expects it
@@ -72,6 +78,9 @@ Returns a new `Napi::Buffer` object.
7278
7379
### New
7480
81+
> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available.
82+
> See [External Buffer][] for more information.
83+
7584
Wraps the provided external data into a new `Napi::Buffer` object.
7685
7786
The `Napi::Buffer` object does not assume ownership for the data and expects it to be
@@ -98,6 +107,93 @@ static Napi::Buffer<T> Napi::Buffer::New(napi_env env,
98107

99108
Returns a new `Napi::Buffer` object.
100109

110+
### NewOrCopy
111+
112+
Wraps the provided external data into a new `Napi::Buffer` object. When the
113+
[external buffer][] is not supported, allocates a new `Napi::Buffer` object and
114+
copies the provided external data into it.
115+
116+
The `Napi::Buffer` object does not assume ownership for the data and expects it to be
117+
valid for the lifetime of the object. Since the `Napi::Buffer` is subject to garbage
118+
collection this overload is only suitable for data which is static and never
119+
needs to be freed.
120+
121+
This factory method will not provide the caller with an opportunity to free the
122+
data when the `Napi::Buffer` gets garbage-collected. If you need to free the
123+
data retained by the `Napi::Buffer` object please use other variants of the
124+
`Napi::Buffer::New` factory method that accept `Napi::Finalizer`, which is a
125+
function that will be invoked when the `Napi::Buffer` object has been
126+
destroyed.
127+
128+
```cpp
129+
static Napi::Buffer<T> Napi::Buffer::NewOrCopy(napi_env env, T* data, size_t length);
130+
```
131+
132+
- `[in] env`: The environment in which to create the `Napi::Buffer` object.
133+
- `[in] data`: The pointer to the external data to expose.
134+
- `[in] length`: The number of `T` elements in the external data.
135+
136+
Returns a new `Napi::Buffer` object.
137+
138+
### NewOrCopy
139+
140+
Wraps the provided external data into a new `Napi::Buffer` object. When the
141+
[external buffer][] is not supported, allocates a new `Napi::Buffer` object and
142+
copies the provided external data into it and the `finalizeCallback` is invoked
143+
immediately.
144+
145+
The `Napi::Buffer` object does not assume ownership for the data and expects it
146+
to be valid for the lifetime of the object. The data can only be freed once the
147+
`finalizeCallback` is invoked to indicate that the `Napi::Buffer` has been released.
148+
149+
```cpp
150+
template <typename Finalizer>
151+
static Napi::Buffer<T> Napi::Buffer::NewOrCopy(napi_env env,
152+
T* data,
153+
size_t length,
154+
Finalizer finalizeCallback);
155+
```
156+
157+
- `[in] env`: The environment in which to create the `Napi::Buffer` object.
158+
- `[in] data`: The pointer to the external data to expose.
159+
- `[in] length`: The number of `T` elements in the external data.
160+
- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is
161+
destroyed. It must implement `operator()`, accept an Napi::Env, a `T*` (which is the
162+
external data pointer), and return `void`.
163+
164+
Returns a new `Napi::Buffer` object.
165+
166+
### NewOrCopy
167+
168+
Wraps the provided external data into a new `Napi::Buffer` object. When the
169+
[external buffer][] is not supported, allocates a new `Napi::Buffer` object and
170+
copies the provided external data into it and the `finalizeCallback` is invoked
171+
immediately.
172+
173+
The `Napi::Buffer` object does not assume ownership for the data and expects it to be
174+
valid for the lifetime of the object. The data can only be freed once the
175+
`finalizeCallback` is invoked to indicate that the `Napi::Buffer` has been released.
176+
177+
```cpp
178+
template <typename Finalizer, typename Hint>
179+
static Napi::Buffer<T> Napi::Buffer::NewOrCopy(napi_env env,
180+
T* data,
181+
size_t length,
182+
Finalizer finalizeCallback,
183+
Hint* finalizeHint);
184+
```
185+
186+
- `[in] env`: The environment in which to create the `Napi::Buffer` object.
187+
- `[in] data`: The pointer to the external data to expose.
188+
- `[in] length`: The number of `T` elements in the external data.
189+
- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is
190+
destroyed. It must implement `operator()`, accept an Napi::Env, a `T*` (which is the
191+
external data pointer) and `Hint*`, and return `void`.
192+
- `[in] finalizeHint`: The hint to be passed as the second parameter of the
193+
finalize callback.
194+
195+
Returns a new `Napi::Buffer` object.
196+
101197
### Copy
102198
103199
Allocates a new `Napi::Buffer` object and copies the provided external data into it.
@@ -148,3 +244,4 @@ size_t Napi::Buffer::Length() const;
148244
Returns the number of `T` elements in the external data.
149245

150246
[`Napi::Uint8Array`]: ./typed_array_of.md
247+
[External Buffer]: ./external_buffer.md

doc/external_buffer.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# External Buffer
2+
3+
**Some runtimes other than Node.js have dropped support for external buffers**.
4+
On runtimes other than Node.js, node-api methods may return
5+
`napi_no_external_buffers_allowed` to indicate that external
6+
buffers are not supported. One such runtime is Electron as
7+
described in this issue
8+
[electron/issues/35801](https://github.com/electron/electron/issues/35801).
9+
10+
In order to maintain broadest compatibility with all runtimes,
11+
you may define `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` in your addon before
12+
includes for the node-api and node-addon-api headers. Doing so will hide the
13+
functions that create external buffers. This will ensure a compilation error
14+
occurs if you accidentally use one of these methods.
15+
16+
In node-addon-api, the `Napi::Buffer::NewOrCopy` provides a convenient way to
17+
create an external buffer, or allocate a new buffer and copy the data when the
18+
external buffer is not supported.

napi-inl.h

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ namespace Napi {
2222
namespace NAPI_CPP_CUSTOM_NAMESPACE {
2323
#endif
2424

25-
// Helpers to handle functions exposed from C++.
25+
// Helpers to handle functions exposed from C++ and internal constants.
2626
namespace details {
2727

28+
// New napi_status constants not yet available in all supported versions of
29+
// Node.js releases. Only necessary when they are used in napi.h and napi-inl.h.
30+
constexpr int napi_no_external_buffers_allowed = 22;
31+
2832
// Attach a data item to an object and delete it when the object gets
2933
// garbage-collected.
3034
// TODO: Replace this code with `napi_add_finalizer()` whenever it becomes
@@ -1756,6 +1760,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, size_t byteLength) {
17561760
return ArrayBuffer(env, value);
17571761
}
17581762

1763+
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
17591764
inline ArrayBuffer ArrayBuffer::New(napi_env env,
17601765
void* externalData,
17611766
size_t byteLength) {
@@ -1815,6 +1820,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env,
18151820

18161821
return ArrayBuffer(env, value);
18171822
}
1823+
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
18181824

18191825
inline ArrayBuffer::ArrayBuffer() : Object() {}
18201826

@@ -2434,6 +2440,7 @@ inline Buffer<T> Buffer<T>::New(napi_env env, size_t length) {
24342440
return Buffer(env, value, length, static_cast<T*>(data));
24352441
}
24362442

2443+
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
24372444
template <typename T>
24382445
inline Buffer<T> Buffer<T>::New(napi_env env, T* data, size_t length) {
24392446
napi_value value;
@@ -2491,6 +2498,94 @@ inline Buffer<T> Buffer<T>::New(napi_env env,
24912498
}
24922499
return Buffer(env, value, length, data);
24932500
}
2501+
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
2502+
2503+
template <typename T>
2504+
inline Buffer<T> Buffer<T>::NewOrCopy(napi_env env, T* data, size_t length) {
2505+
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
2506+
napi_value value;
2507+
napi_status status = napi_create_external_buffer(
2508+
env, length * sizeof(T), data, nullptr, nullptr, &value);
2509+
if (status == details::napi_no_external_buffers_allowed) {
2510+
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
2511+
// If we can't create an external buffer, we'll just copy the data.
2512+
return Buffer<T>::Copy(env, data, length);
2513+
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
2514+
}
2515+
NAPI_THROW_IF_FAILED(env, status, Buffer<T>());
2516+
return Buffer(env, value, length, data);
2517+
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
2518+
}
2519+
2520+
template <typename T>
2521+
template <typename Finalizer>
2522+
inline Buffer<T> Buffer<T>::NewOrCopy(napi_env env,
2523+
T* data,
2524+
size_t length,
2525+
Finalizer finalizeCallback) {
2526+
details::FinalizeData<T, Finalizer>* finalizeData =
2527+
new details::FinalizeData<T, Finalizer>(
2528+
{std::move(finalizeCallback), nullptr});
2529+
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
2530+
napi_value value;
2531+
napi_status status =
2532+
napi_create_external_buffer(env,
2533+
length * sizeof(T),
2534+
data,
2535+
details::FinalizeData<T, Finalizer>::Wrapper,
2536+
finalizeData,
2537+
&value);
2538+
if (status == details::napi_no_external_buffers_allowed) {
2539+
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
2540+
// If we can't create an external buffer, we'll just copy the data.
2541+
Buffer<T> ret = Buffer<T>::Copy(env, data, length);
2542+
details::FinalizeData<T, Finalizer>::Wrapper(env, data, finalizeData);
2543+
return ret;
2544+
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
2545+
}
2546+
if (status != napi_ok) {
2547+
delete finalizeData;
2548+
NAPI_THROW_IF_FAILED(env, status, Buffer());
2549+
}
2550+
return Buffer(env, value, length, data);
2551+
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
2552+
}
2553+
2554+
template <typename T>
2555+
template <typename Finalizer, typename Hint>
2556+
inline Buffer<T> Buffer<T>::NewOrCopy(napi_env env,
2557+
T* data,
2558+
size_t length,
2559+
Finalizer finalizeCallback,
2560+
Hint* finalizeHint) {
2561+
details::FinalizeData<T, Finalizer, Hint>* finalizeData =
2562+
new details::FinalizeData<T, Finalizer, Hint>(
2563+
{std::move(finalizeCallback), finalizeHint});
2564+
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
2565+
napi_value value;
2566+
napi_status status = napi_create_external_buffer(
2567+
env,
2568+
length * sizeof(T),
2569+
data,
2570+
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint,
2571+
finalizeData,
2572+
&value);
2573+
if (status == details::napi_no_external_buffers_allowed) {
2574+
#endif
2575+
// If we can't create an external buffer, we'll just copy the data.
2576+
Buffer<T> ret = Buffer<T>::Copy(env, data, length);
2577+
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint(
2578+
env, data, finalizeData);
2579+
return ret;
2580+
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
2581+
}
2582+
if (status != napi_ok) {
2583+
delete finalizeData;
2584+
NAPI_THROW_IF_FAILED(env, status, Buffer());
2585+
}
2586+
return Buffer(env, value, length, data);
2587+
#endif
2588+
}
24942589

24952590
template <typename T>
24962591
inline Buffer<T> Buffer<T>::Copy(napi_env env, const T* data, size_t length) {

napi.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,6 +1077,7 @@ class ArrayBuffer : public Object {
10771077
size_t byteLength ///< Length of the buffer to be allocated, in bytes
10781078
);
10791079

1080+
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
10801081
/// Creates a new ArrayBuffer instance, using an external buffer with
10811082
/// specified byte length.
10821083
static ArrayBuffer New(
@@ -1120,6 +1121,7 @@ class ArrayBuffer : public Object {
11201121
Hint* finalizeHint ///< Hint (second parameter) to be passed to the
11211122
///< finalize callback
11221123
);
1124+
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
11231125

11241126
ArrayBuffer(); ///< Creates a new _empty_ ArrayBuffer instance.
11251127
ArrayBuffer(napi_env env,
@@ -1432,6 +1434,7 @@ template <typename T>
14321434
class Buffer : public Uint8Array {
14331435
public:
14341436
static Buffer<T> New(napi_env env, size_t length);
1437+
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
14351438
static Buffer<T> New(napi_env env, T* data, size_t length);
14361439

14371440
// Finalizer must implement `void operator()(Env env, T* data)`.
@@ -1447,6 +1450,22 @@ class Buffer : public Uint8Array {
14471450
size_t length,
14481451
Finalizer finalizeCallback,
14491452
Hint* finalizeHint);
1453+
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
1454+
1455+
static Buffer<T> NewOrCopy(napi_env env, T* data, size_t length);
1456+
// Finalizer must implement `void operator()(Env env, T* data)`.
1457+
template <typename Finalizer>
1458+
static Buffer<T> NewOrCopy(napi_env env,
1459+
T* data,
1460+
size_t length,
1461+
Finalizer finalizeCallback);
1462+
// Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`.
1463+
template <typename Finalizer, typename Hint>
1464+
static Buffer<T> NewOrCopy(napi_env env,
1465+
T* data,
1466+
size_t length,
1467+
Finalizer finalizeCallback,
1468+
Hint* finalizeHint);
14501469

14511470
static Buffer<T> Copy(napi_env env, const T* data, size_t length);
14521471

test/binding.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Object InitBasicTypesValue(Env env);
2222
Object InitBigInt(Env env);
2323
#endif
2424
Object InitBuffer(Env env);
25+
Object InitBufferNoExternal(Env env);
2526
#if (NAPI_VERSION > 2)
2627
Object InitCallbackScope(Env env);
2728
#endif
@@ -107,6 +108,7 @@ Object Init(Env env, Object exports) {
107108
exports.Set("date", InitDate(env));
108109
#endif
109110
exports.Set("buffer", InitBuffer(env));
111+
exports.Set("bufferNoExternal", InitBufferNoExternal(env));
110112
#if (NAPI_VERSION > 2)
111113
exports.Set("callbackscope", InitCallbackScope(env));
112114
#endif

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
'callbackInfo.cc',
2121
'date.cc',
2222
'binding.cc',
23+
'buffer_no_external.cc',
2324
'buffer.cc',
2425
'callbackscope.cc',
2526
'dataview/dataview.cc',

0 commit comments

Comments
 (0)