16
votes

The Emscripten tutorial give a good explanation of how to interact with C functions: https://github.com/kripken/emscripten/wiki/Interacting-with-code

But how do you interact with C++ classes:

  • Call a constructor to create an object
  • Delete an obj
  • Prevent dead code elimination of classes and its methods
2
This should be tackled soon by embind. I think you may have a look at github.com/kripken/emscripten/tree/master/tests/embind but not sure how current it is. - abergmeier
I saw the above comment after writing my answer. Looks like there are now some docs on the matter here. I'll look into using embind when I get a chance. - lakenen
Short reason this is hard, google for c++ name mangling. - meawoppl

2 Answers

17
votes

Check this : http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html

Example :

C++ code :

#include <emscripten/bind.h>

using namespace emscripten;

class MyClass {
public:
    MyClass(int x, std::string y)
        : x(x)
        , y(y)
    {}

    void incrementX() {
        ++x;
    }

    int getX() const { return x; }
    void setX(int x_) { x = x_; }

    static std::string getStringFromInstance(const MyClass& instance) {
        return instance.y;
    }

private:
    int x;
    std::string y;
};

EMSCRIPTEN_BINDINGS(my_class_example) {
    class_<MyClass>("MyClass")
        .constructor<int, std::string>()
        .function("incrementX", &MyClass::incrementX)
        .property("x", &MyClass::getX, &MyClass::setX)
        .class_function("getStringFromInstance", &MyClass::getStringFromInstance)
        ;
}

JS code :

var instance = new Module.MyClass(10, "hello");
instance.incrementX();
instance.x; // 12
instance.x = 20; // 20
Module.MyClass.getStringFromInstance(instance); // "hello"
instance.delete();
6
votes

The way I did this was to create "proxy" functions that do the necessary operations. For example:

class HelloWorld
{
    int x;
  public:
    HelloWorld() { x = 0; }
    ~HelloWorld() {}
    void setX(int v) { x = v; }
    int getX() { return x; }
    // ...
};


//compile using "C" linkage to avoid name obfuscation
extern "C" {
  //constructor, returns a pointer to the HelloWorld object
  void *HW_constructor() {
    return new HelloWorld();
  }

  void HW_setX(HelloWorld *hw, int x) {
    hw->setX(x);
  }

  int HW_getX(HelloWorld *hw) {
    return hw->getX();
  }

  void HW_destructor(HelloWorld *hw) {
    delete hw;
  }
};

Then in JS, you have to build a clone of your object that calls the proxy functions (annoying, I know, but I don't know of a better solution at the moment):

// get references to the exposed proxy functions
var HW_constructor = Module.cwrap('HW_constructor', 'number', []);
var HW_destructor = Module.cwrap('HW_destructor', null, ['number']);
var HW_setX = Module.cwrap('HW_setX', null, ['number', 'number']);
var HW_getX = Module.cwrap('HW_getX', 'number', ['number']);

function HelloWorld() {
  this.ptr = HW_constructor();
}

HelloWorld.prototype.destroy = function () {
  HW_destructor(this.ptr);
};

HelloWorld.prototype.setX = function (x) {
  HW_setX(this.ptr, x);
};

HelloWorld.prototype.getX = function () {
  return HW_getX(this.ptr);
};

IMPORTANT Keep in mind, in order for this to work, you need to add the following flag to your emcc command to tell it to not strip out the proxy methods as dead code (NOTE: the underscore here is intentional and important!):

emcc helloworld.cpp -o helloworld.js \
  -s EXPORTED_FUNCTIONS="['_HW_constructor','_HW_destructor','_HW_setX','_HW_getX']"

EDIT: I created a gist for folks to try out the code.