Skip to content

Commit fc28d94

Browse files
addaleaxnodejs-github-bot
authored andcommitted
src: officially deprecate node::ObjectWrap
This interface has long been unmaintained and doesn't match current best practices for C++ object management. It's also simply not a good idea to have this header be part of Node.js core; since it is an inline header, user code will depend on the Node.js version it was compiled with, and there is no way to adopt changes to this header independently of changing the target Node.js version. Better userland alternatives exist and have long been more popular, notably, `node-addon-api` and, for use cases where direct V8 API access is required, `nan`. Signed-off-by: Anna Henningsen <anna@addaleax.net> PR-URL: #63756 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent d097de8 commit fc28d94

3 files changed

Lines changed: 100 additions & 22 deletions

File tree

doc/api/addons.md

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ There are three options for implementing addons:
1212

1313
* [Node-API][] (recommended)
1414
* `nan` ([Native Abstractions for Node.js][])
15-
* direct use of internal V8, libuv, and Node.js libraries
15+
* direct use of public V8, libuv, and Node.js interfaces
1616

1717
This rest of this document focuses on the latter, requiring
1818
knowledge of multiple components and APIs:
@@ -35,8 +35,8 @@ knowledge of multiple components and APIs:
3535
offloading work via libuv to non-blocking system operations, worker threads,
3636
or a custom use of libuv threads.
3737

38-
* Internal Node.js libraries: Node.js itself exports C++ APIs that addons can
39-
use, the most important of which is the `node::ObjectWrap` class.
38+
* Public Node.js interfaces: Node.js itself exports C++ APIs that addons
39+
and embedders can make use of.
4040

4141
* Other statically linked libraries (including OpenSSL): These
4242
other libraries are located in the `deps/` directory in the Node.js source
@@ -815,7 +815,8 @@ NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)
815815
} // namespace demo
816816
```
817817
818-
Then, in `myobject.h`, the wrapper class inherits from `node::ObjectWrap`:
818+
Then, in `myobject.h`, the wrapper class inherits from a helper `ObjectWrap`
819+
which handles tying the lifetime of the C++ object to the exposed JS object:
819820
820821
<!-- addon-verify-file wrapping_c_objects/myobject.h -->
821822
@@ -825,11 +826,11 @@ Then, in `myobject.h`, the wrapper class inherits from `node::ObjectWrap`:
825826
#define MYOBJECT_H
826827
827828
#include <node.h>
828-
#include <node_object_wrap.h>
829+
#include "object_wrap.h"
829830
830831
namespace demo {
831832
832-
class MyObject : public node::ObjectWrap {
833+
class MyObject : public ObjectWrap {
833834
public:
834835
static void Init(v8::Local<v8::Object> exports);
835836
@@ -848,6 +849,68 @@ class MyObject : public node::ObjectWrap {
848849
#endif
849850
```
850851

852+
where `ObjectWrap` is defined as
853+
854+
<!-- addon-verify-file wrapping_c_objects/object_wrap.h factory_of_wrapped_objects/object_wrap.h passing_wrapped_objects_around/object_wrap.h -->
855+
856+
```cpp
857+
// object_wrap.h
858+
#ifndef OBJECTWRAP_H
859+
#define OBJECTWRAP_H
860+
861+
#include <node.h>
862+
863+
namespace demo {
864+
865+
class ObjectWrap {
866+
public:
867+
ObjectWrap() : isolate_(v8::Isolate::GetCurrent()) {
868+
node::AddEnvironmentCleanupHook(isolate_, CleanupHook, this);
869+
}
870+
871+
virtual ~ObjectWrap() {
872+
node::RemoveEnvironmentCleanupHook(isolate_, CleanupHook, this);
873+
}
874+
875+
template <class T>
876+
static T* Unwrap(v8::Local<v8::Object> handle) {
877+
void* ptr = handle->GetAlignedPointerFromInternalField(
878+
0, v8::kEmbedderDataTypeTagDefault);
879+
ObjectWrap* wrap = static_cast<ObjectWrap*>(ptr);
880+
return static_cast<T*>(wrap);
881+
}
882+
883+
v8::Local<v8::Object> object() {
884+
return handle_.Get(isolate_);
885+
}
886+
887+
protected:
888+
inline void Wrap(v8::Local<v8::Object> handle) {
889+
handle->SetAlignedPointerInInternalField(
890+
0, this, v8::kEmbedderDataTypeTagDefault);
891+
handle_.Reset(isolate_, handle);
892+
}
893+
894+
inline void MakeWeak() {
895+
handle_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
896+
}
897+
898+
private:
899+
static void WeakCallback(
900+
const v8::WeakCallbackInfo<ObjectWrap>& data) {
901+
delete data.GetParameter();
902+
}
903+
904+
static void CleanupHook(void* arg) { delete static_cast<ObjectWrap*>(arg); }
905+
906+
v8::Global<v8::Object> handle_;
907+
v8::Isolate* isolate_;
908+
};
909+
910+
} // namespace demo
911+
#endif
912+
```
913+
851914
In `myobject.cc`, implement the various methods that are to be exposed.
852915
In the following code, the method `plusOne()` is exposed by adding it to the
853916
constructor's prototype:
@@ -912,6 +975,7 @@ void MyObject::New(const FunctionCallbackInfo<Value>& args) {
912975
0 : args[0]->NumberValue(context).FromMaybe(0);
913976
MyObject* obj = new MyObject(value);
914977
obj->Wrap(args.This());
978+
obj->MakeWeak();
915979
args.GetReturnValue().Set(args.This());
916980
} else {
917981
// Invoked as plain function `MyObject(...)`, turn into construct call.
@@ -1039,11 +1103,11 @@ JavaScript:
10391103
#define MYOBJECT_H
10401104
10411105
#include <node.h>
1042-
#include <node_object_wrap.h>
1106+
#include "object_wrap.h"
10431107
10441108
namespace demo {
10451109
1046-
class MyObject : public node::ObjectWrap {
1110+
class MyObject : public ObjectWrap {
10471111
public:
10481112
static void Init();
10491113
static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -1063,7 +1127,8 @@ class MyObject : public node::ObjectWrap {
10631127
#endif
10641128
```
10651129

1066-
The implementation in `myobject.cc` is similar to the previous example:
1130+
Our `ObjectWrap` helper remains the same as in the previous example,
1131+
and implementation in `myobject.cc` is similar as well:
10671132

10681133
<!-- addon-verify-file factory_of_wrapped_objects/myobject.cc -->
10691134

@@ -1125,6 +1190,7 @@ void MyObject::New(const FunctionCallbackInfo<Value>& args) {
11251190
0 : args[0]->NumberValue(context).FromMaybe(0);
11261191
MyObject* obj = new MyObject(value);
11271192
obj->Wrap(args.This());
1193+
obj->MakeWeak();
11281194
args.GetReturnValue().Set(args.This());
11291195
} else {
11301196
// Invoked as plain function `MyObject(...)`, turn into construct call.
@@ -1208,7 +1274,7 @@ console.log(obj2.plusOne());
12081274

12091275
In addition to wrapping and returning C++ objects, it is possible to pass
12101276
wrapped objects around by unwrapping them with the Node.js helper function
1211-
`node::ObjectWrap::Unwrap`. The following examples shows a function `add()`
1277+
`ObjectWrap::Unwrap`. The following examples shows a function `add()`
12121278
that can take two `MyObject` objects as input arguments:
12131279

12141280
<!-- addon-verify-file passing_wrapped_objects_around/addon.cc -->
@@ -1238,9 +1304,9 @@ void Add(const FunctionCallbackInfo<Value>& args) {
12381304
Isolate* isolate = args.GetIsolate();
12391305
Local<Context> context = isolate->GetCurrentContext();
12401306

1241-
MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(
1307+
MyObject* obj1 = ObjectWrap::Unwrap<MyObject>(
12421308
args[0]->ToObject(context).ToLocalChecked());
1243-
MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(
1309+
MyObject* obj2 = ObjectWrap::Unwrap<MyObject>(
12441310
args[1]->ToObject(context).ToLocalChecked());
12451311

12461312
double sum = obj1->value() + obj2->value();
@@ -1270,11 +1336,11 @@ after unwrapping the object.
12701336
#define MYOBJECT_H
12711337
12721338
#include <node.h>
1273-
#include <node_object_wrap.h>
1339+
#include "object_wrap.h"
12741340
12751341
namespace demo {
12761342
1277-
class MyObject : public node::ObjectWrap {
1343+
class MyObject : public ObjectWrap {
12781344
public:
12791345
static void Init();
12801346
static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -1294,7 +1360,8 @@ class MyObject : public node::ObjectWrap {
12941360
#endif
12951361
```
12961362

1297-
The implementation of `myobject.cc` remains similar to the previous version:
1363+
Our `ObjectWrap` helper remains the same as in the previous example,
1364+
and implementation in `myobject.cc` is similar as well:
12981365

12991366
<!-- addon-verify-file passing_wrapped_objects_around/myobject.cc -->
13001367

@@ -1352,6 +1419,7 @@ void MyObject::New(const FunctionCallbackInfo<Value>& args) {
13521419
0 : args[0]->NumberValue(context).FromMaybe(0);
13531420
MyObject* obj = new MyObject(value);
13541421
obj->Wrap(args.This());
1422+
obj->MakeWeak();
13551423
args.GetReturnValue().Set(args.This());
13561424
} else {
13571425
// Invoked as plain function `MyObject(...)`, turn into construct call.

src/node_object_wrap.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@
2727

2828
namespace node {
2929

30-
class ObjectWrap {
30+
// Legacy interface for tying C++ objects to JavaScript objects.
31+
// This is not intended to be used by new code, and userland alternatives
32+
// should be preferred.
33+
class NODE_DEPRECATED(
34+
"Use userland alternatives such as node-addon-api or nan instead",
35+
ObjectWrap) {
3136
public:
3237
ObjectWrap() {
3338
refs_ = 0;

tools/doc/addon-verify.mjs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//
66
// Each code block to extract is preceded by a marker in the markdown:
77
//
8-
// <!-- addon-verify-file worker_support/addon.cc -->
8+
// <!-- addon-verify-file worker_support/addon.cc [...] -->
99
// ```cpp
1010
// #include <node.h>
1111
// ...
@@ -33,7 +33,7 @@ if (!values.input || !values.output) {
3333

3434
const src = await open(values.input, 'r');
3535

36-
const MARKER_RE = /^<!--\s*addon-verify-file\s+(\S+?)\/(\S+)\s*-->$/;
36+
const MARKER_RE = /^<!--\s*addon-verify-file\s+(?<filenames>((\S+?)\/(\S+)\s?)+)\s*-->$/;
3737

3838
const entries = [];
3939
let nextBlockIsAddonVerifyFile = false;
@@ -47,7 +47,7 @@ for await (const line of src.readLines()) {
4747
continue;
4848
}
4949

50-
entries.at(-1).content += `${line}\n`;
50+
entries.at(-1).contentRef.content += `${line}\n`;
5151
}
5252
if (nextBlockIsAddonVerifyFile) {
5353
if (line) {
@@ -59,8 +59,13 @@ for await (const line of src.readLines()) {
5959
const match = MARKER_RE.exec(line);
6060
if (match) {
6161
nextBlockIsAddonVerifyFile = true;
62-
const [, dir, name] = match;
63-
entries.push({ dir, name, content: '' });
62+
const { filenames } = match.groups;
63+
// Use a single contentRef for files with identical contents
64+
const contentRef = { content: '' };
65+
for (const filename of filenames.split(/\s+/).filter(Boolean)) {
66+
const [dir, name] = filename.split('/');
67+
entries.push({ dir, name, contentRef });
68+
}
6469
}
6570
}
6671

@@ -74,7 +79,7 @@ for (const [name, files] of sections) {
7479
mkdirSync(dir, { recursive: true });
7580

7681
for (const file of files) {
77-
let content = file.content;
82+
let { content } = file.contentRef;
7883
if (file.name === 'test.js') {
7984
content =
8085
"'use strict';\n" +

0 commit comments

Comments
 (0)