emscripten-core / emscripten-core/emscripten

Embind: extend vs implement - double delete()

Open
#25,342 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
27.6k
Forks
3.6k
Avg merge
1d 1h
Merged PRs (30d)
105

Description

Coming from the C++ world, I'm struggling to understand which of the two options is correct when I'm trying to bind the pure virtual C++ class and provide the implementation on the JS side.

Suppose you have an interface like this:
```cpp
// main.cpp
#include
#include

class IAction {
public:
virtual ~IAction() {
std::cout << "IAction::~IAction()" << std::endl;
}
virtual void PrintSelf() const = 0;
};

class IActionWrapper : public emscripten::wrapper {
public:
EMSCRIPTEN_WRAPPER(IActionWrapper);
~IActionWrapper() override = default;
void PrintSelf() const override {
return call("PrintSelf");
}
};

EMSCRIPTEN_BINDINGS(Factory) {
using namespace emscripten;

class_("IAction")
.function("PrintSelf", &IAction::PrintSelf, pure_virtual())
.allow_subclass("IActionWrapper");
}
```

And these are the two possible JS implementations that follow the embind docs example, one using `extend` and the other one `implement`:
```js
const ActionExtend = Module.IAction.extend("IAction", {
__construct: function(name: string) {
this.__parent.__construct.call(this);
this.name = name;
},
__destruct: function() {
this.__parent.__destruct.call(this);
},
PrintSelf() {
console.log("Action:", this.name);
}
})

const ActionImplement = (name: string) => {
return Module.IAction.implement({
name: name,
PrintSelf() {
console.log("Action:", this.name);
}
});
}

const A1 = new ActionExtend("A1");
A1.PrintSelf();
console.log("Action is deleted: ", A1.isDeleted())
A1.delete();
console.log("Action is deleted: ", A1.isDeleted())

const A2 = ActionImplement("A2");
A2.PrintSelf();
console.log("Action is deleted: ", A2.isDeleted())
A2.delete();
console.log("Action is deleted: ", A2.isDeleted())
```

This is the log that I see in the console:
```txt
Action: A1
Action is deleted: false
IAction::~IAction()
Action is deleted: false

Action: A2
Action is deleted: false
IAction::~IAction()
Action is deleted: true
```

Based on the log it seems like the `implement` version is the correct one. The `extend` even allows the double call of `.delete()` without any errors for the 2nd call, although the `IAction`'s destructor is only triggered once.

```js
const A3 = new ActionExtend("A3");
A3.PrintSelf();
console.log("Action is deleted: ", A3.isDeleted())
A3.delete();
console.log("Action is deleted: ", A3.isDeleted())
A3.delete(); // Double delete - still valid, no dtor called
```

Is this all correct and works as expected?

**Version of emscripten/emsdk:**
Emscripten 4.0.15

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.