Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 83 additions & 15 deletions doc/api/addons.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ There are three options for implementing addons:

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

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

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

* Other statically linked libraries (including OpenSSL): These
other libraries are located in the `deps/` directory in the Node.js source
Expand Down Expand Up @@ -815,7 +815,8 @@ NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)
} // namespace demo
```

Then, in `myobject.h`, the wrapper class inherits from `node::ObjectWrap`:
Then, in `myobject.h`, the wrapper class inherits from a helper `ObjectWrap`
which handles tying the lifetime of the C++ object to the exposed JS object:

<!-- addon-verify-file wrapping_c_objects/myobject.h -->

Expand All @@ -825,11 +826,11 @@ Then, in `myobject.h`, the wrapper class inherits from `node::ObjectWrap`:
#define MYOBJECT_H

#include <node.h>
#include <node_object_wrap.h>
#include "object_wrap.h"

namespace demo {

class MyObject : public node::ObjectWrap {
class MyObject : public ObjectWrap {
public:
static void Init(v8::Local<v8::Object> exports);

Expand All @@ -848,6 +849,68 @@ class MyObject : public node::ObjectWrap {
#endif
```

where `ObjectWrap` is defined as

<!-- addon-verify-file wrapping_c_objects/object_wrap.h factory_of_wrapped_objects/object_wrap.h passing_wrapped_objects_around/object_wrap.h -->

```cpp
// object_wrap.h
#ifndef OBJECTWRAP_H
#define OBJECTWRAP_H

#include <node.h>

namespace demo {

class ObjectWrap {
public:
ObjectWrap() : isolate_(v8::Isolate::GetCurrent()) {
node::AddEnvironmentCleanupHook(isolate_, CleanupHook, this);
}

virtual ~ObjectWrap() {
node::RemoveEnvironmentCleanupHook(isolate_, CleanupHook, this);
}

template <class T>
static T* Unwrap(v8::Local<v8::Object> handle) {
void* ptr = handle->GetAlignedPointerFromInternalField(
0, v8::kEmbedderDataTypeTagDefault);
ObjectWrap* wrap = static_cast<ObjectWrap*>(ptr);
return static_cast<T*>(wrap);
}

v8::Local<v8::Object> object() {
return handle_.Get(isolate_);
}

protected:
inline void Wrap(v8::Local<v8::Object> handle) {
handle->SetAlignedPointerInInternalField(
0, this, v8::kEmbedderDataTypeTagDefault);
handle_.Reset(isolate_, handle);
}

inline void MakeWeak() {
handle_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
}

private:
static void WeakCallback(
const v8::WeakCallbackInfo<ObjectWrap>& data) {
delete data.GetParameter();
}

static void CleanupHook(void* arg) { delete static_cast<ObjectWrap*>(arg); }

v8::Global<v8::Object> handle_;
v8::Isolate* isolate_;
};

} // namespace demo
#endif
```

In `myobject.cc`, implement the various methods that are to be exposed.
In the following code, the method `plusOne()` is exposed by adding it to the
constructor's prototype:
Expand Down Expand Up @@ -912,6 +975,7 @@ void MyObject::New(const FunctionCallbackInfo<Value>& args) {
0 : args[0]->NumberValue(context).FromMaybe(0);
MyObject* obj = new MyObject(value);
obj->Wrap(args.This());
obj->MakeWeak();
args.GetReturnValue().Set(args.This());
} else {
// Invoked as plain function `MyObject(...)`, turn into construct call.
Expand Down Expand Up @@ -1039,11 +1103,11 @@ JavaScript:
#define MYOBJECT_H

#include <node.h>
#include <node_object_wrap.h>
#include "object_wrap.h"

namespace demo {

class MyObject : public node::ObjectWrap {
class MyObject : public ObjectWrap {
public:
static void Init();
static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
Expand All @@ -1063,7 +1127,8 @@ class MyObject : public node::ObjectWrap {
#endif
```

The implementation in `myobject.cc` is similar to the previous example:
Our `ObjectWrap` helper remains the same as in the previous example,
and implementation in `myobject.cc` is similar as well:

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

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

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

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

MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(
MyObject* obj1 = ObjectWrap::Unwrap<MyObject>(
args[0]->ToObject(context).ToLocalChecked());
MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(
MyObject* obj2 = ObjectWrap::Unwrap<MyObject>(
args[1]->ToObject(context).ToLocalChecked());

double sum = obj1->value() + obj2->value();
Expand Down Expand Up @@ -1270,11 +1336,11 @@ after unwrapping the object.
#define MYOBJECT_H

#include <node.h>
#include <node_object_wrap.h>
#include "object_wrap.h"

namespace demo {

class MyObject : public node::ObjectWrap {
class MyObject : public ObjectWrap {
public:
static void Init();
static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
Expand All @@ -1294,7 +1360,8 @@ class MyObject : public node::ObjectWrap {
#endif
```

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

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

Expand Down Expand Up @@ -1352,6 +1419,7 @@ void MyObject::New(const FunctionCallbackInfo<Value>& args) {
0 : args[0]->NumberValue(context).FromMaybe(0);
MyObject* obj = new MyObject(value);
obj->Wrap(args.This());
obj->MakeWeak();
args.GetReturnValue().Set(args.This());
} else {
// Invoked as plain function `MyObject(...)`, turn into construct call.
Expand Down
7 changes: 6 additions & 1 deletion src/node_object_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@

namespace node {

class ObjectWrap {
// Legacy interface for tying C++ objects to JavaScript objects.
// This is not intended to be used by new code, and userland alternatives
// should be preferred.
class NODE_DEPRECATED(
"Use userland alternatives such as node-addon-api or nan instead",
ObjectWrap) {
public:
ObjectWrap() {
refs_ = 0;
Expand Down
17 changes: 11 additions & 6 deletions tools/doc/addon-verify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//
// Each code block to extract is preceded by a marker in the markdown:
//
// <!-- addon-verify-file worker_support/addon.cc -->
// <!-- addon-verify-file worker_support/addon.cc [...] -->
// ```cpp
// #include <node.h>
// ...
Expand Down Expand Up @@ -33,7 +33,7 @@ if (!values.input || !values.output) {

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

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

const entries = [];
let nextBlockIsAddonVerifyFile = false;
Expand All @@ -47,7 +47,7 @@ for await (const line of src.readLines()) {
continue;
}

entries.at(-1).content += `${line}\n`;
entries.at(-1).contentRef.content += `${line}\n`;
}
if (nextBlockIsAddonVerifyFile) {
if (line) {
Expand All @@ -59,8 +59,13 @@ for await (const line of src.readLines()) {
const match = MARKER_RE.exec(line);
if (match) {
nextBlockIsAddonVerifyFile = true;
const [, dir, name] = match;
entries.push({ dir, name, content: '' });
const { filenames } = match.groups;
// Use a single contentRef for files with identical contents
const contentRef = { content: '' };
for (const filename of filenames.split(/\s+/).filter(Boolean)) {
const [dir, name] = filename.split('/');
entries.push({ dir, name, contentRef });
}
}
}

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

for (const file of files) {
let content = file.content;
let { content } = file.contentRef;
if (file.name === 'test.js') {
content =
"'use strict';\n" +
Expand Down
Loading