Skip to content

Embind: extend vs implement - double delete() #25342

@poretga99

Description

@poretga99

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:

// main.cpp
#include <iostream>
#include <emscripten/bind.h>

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

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

EMSCRIPTEN_BINDINGS(Factory) {
    using namespace emscripten;

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

And these are the two possible JS implementations that follow the embind docs example, one using extend and the other one implement:

    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:

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.

    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions