When exposing C++ classes to WebAssembly with Emscripten, is embind the proper choice even if I don’t use JavaScript?

  Kiến thức lập trình

I have a C++ class that I would like to compile to WebAssembly using Emscripten, which I then want to call from an Embedder that is not JavaScript. (For example, from C# using wasmtime).

With C code or global functions, I can just slap an EMSCRIPTEN_KEEPALIVE on them and see them as an export in the generated .wasm file. However, C++ classes don’t seem to work that way, I don’t see anything output to the .wasm. I can put EMSCRIPTEN_KEEPALIVE on the individual class functions, but that seems extremely brittle.

It seems that the preferred way for C++ classes is using embind, but that in turn creates a JavaScript wrapper – but I don’t use JavaScript, so that doesn’t help.

Looking at the .wasm, I see imports for embind methods, like (import "env" "_embind_register_class" (func $env._embind_register_class (type $t11))). These are called from the exported _initialize method that I need to call anyway when instantiating a Emscripten-generated module.

So, if I understand this correctly: If I want to use a C++ class from WebAssembly in an Embedder that’s not JavaScript, and not create C proxy functions (like in this answer), is the only proper option to use embind and port embind.js to my Embedder? And if that is the proper way, is there any guarantee on how stable the Embind API is?

For reference, I compile my code like this:

emcc -std=c++17 -Wall -Werror -O3 --bind --no-entry -s WASM=1 -s EXPORTED_FUNCTIONS="['_malloc', '_free']" -o cpptest.wasm cpptest.cpp

And the cpptest.cpp looks like this:

#include <cstdint>

class FooCounter {
    private:
        uint32_t fooCount;
    public:
        FooCounter() { fooCount = 0; }
        ~FooCounter() { }
        void setFooCount(uint32_t count) { fooCount = count; }
        uint32_t getFooCount() const { return fooCount; }
        uint32_t incrementFooCount(); /* implemented in cpptest.cpp */
        static uint32_t staticFooCount(const FooCounter& ec) {
            return ec.fooCount;
        }
};

#ifdef __EMSCRIPTEN__
#include <emscripten/bind.h>

EMSCRIPTEN_BINDINGS(my_class_example) {
    emscripten::class_<FooCounter>("FooCounter")
        .constructor()
        .function("incrementFooCount", &FooCounter::incrementFooCount)
        .property("fooCount", &FooCounter::getFooCount, &FooCounter::setFooCount)
        .class_function("staticFooCount", &FooCounter::staticFooCount)
        ;
}

#endif

LEAVE A COMMENT