20

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
Mortennobel
  • 3,383
  • 4
  • 29
  • 46
  • 2
    This should be tackled soon by `embind`. I think you may have a look at https://github.com/kripken/emscripten/tree/master/tests/embind but not sure how current it is. – abergmeier Apr 08 '13 at 14:39
  • I saw the above comment after writing my answer. Looks like there are now some docs on the matter [here](https://github.com/imvu/emscripten/wiki/embind). I'll look into using `embind` when I get a chance. – lakenen May 23 '13 at 22:57
  • Short reason this is hard, google for c++ name mangling. – meawoppl Mar 03 '14 at 06:18

2 Answers2

22

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();
19greg96
  • 2,592
  • 5
  • 41
  • 55
Zardoz89
  • 590
  • 5
  • 17
7

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.

lakenen
  • 3,436
  • 5
  • 27
  • 39