javascriptc++emscriptenembind

How to use embind with a String class that isn't std::string?


Emscripten's embind mechanism seems to have pretty nice support for converting JavaScript's native strings to and from C++'s std::string automatically; however, the C++ library that I'm trying to create JavaScript bindings for has its own String class that it uses instead of std::string. Short of rewriting the C++ library to use std::string, is there any way to get embind to convert JavaScript strings to this library's custom String class the same way it handles conversions to/from std::string ?

I think it should be possible by declaring an appropriate BindingType template-struct that contains the necessary conversion code, but I haven't had any luck getting it to work.

A minimal, self-contained example of what I've tried so far is listed below; when I load the index.html page, Safari gives me the error Unhandled Promise Rejection: BindingError: Cannot pass "ABC" as a ContrivedExampleString. The behavior I'd like to see instead is that it prints XXX ABC to the JavaScript console.

#include <cstdlib>
#include <string>
#include <emscripten/bind.h>
#include <emscripten/wire.h>

namespace SomeLibrary {

/** Some library's own String class, oversimplified here for illustration purposes only */
class ContrivedExampleString
{
public:
   ContrivedExampleString()                   : _str(NULL)      {/* empty */}
   ContrivedExampleString(const char * s)     : _str(strdup(s)) {/* empty */}
   ContrivedExampleString(const ContrivedExampleString & rhs) : _str(NULL)      {*this = rhs;}

   ~ContrivedExampleString() {if (_str) free(_str);}

   ContrivedExampleString & operator = (const ContrivedExampleString & rhs)
   {
      if (&rhs != this)
      {
         if (_str) free(_str);
         _str = rhs._str ? strdup(rhs._str) : NULL;
      }
      return *this;
   }

   const char * CStr() const {return _str ? _str : "";}

   size_t Length() const {return _str ? strlen(_str) : 0;}

private:
   char * _str;
};

/** Contrived example class */
class ContrivedExampleClass
{
public:
   ContrivedExampleClass() {/* empty */}

   ContrivedExampleString ContrivedExampleFunction(const ContrivedExampleString & s) const
   {
      return s;
   }
};

};  // end SomeLibrary namespace

using namespace emscripten;
using namespace SomeLibrary;

// Tell embind how to autoconvert ContrivedExampleString <-> JavaScript string
template <> struct emscripten::internal::BindingType<ContrivedExampleString>
{
    typedef struct
    {
        size_t length;
        char data[]; // trailing data
    } * WireType;

    static WireType toWireType(const std::string & v, rvp::default_tag)
    {
        WireType wt = (WireType) malloc(sizeof(size_t) + v.length() +1);
        wt->length = v.length();
        memcpy(wt->data, v.c_str(), v.length()+1);
        return wt;
    }

    static WireType toWireType(const ContrivedExampleString & v, rvp::default_tag)
    {
        WireType wt = (WireType) malloc(sizeof(size_t) + v.Length() +1);
        wt->length = v.Length();
        memcpy(wt->data, v.CStr(), v.Length()+1);
        return wt;
    }

    static ContrivedExampleString fromWireType(WireType v) {
        //return std::string(v->data, v->length);
        return ContrivedExampleString(v->data);
    }
};

EMSCRIPTEN_BINDINGS(ContrivedExampleClass) {
   class_<ContrivedExampleClass>("ContrivedExampleClass")
      .constructor<>()
      .function("ContrivedExampleFunction", &ContrivedExampleClass::ContrivedExampleFunction);
}

FWIW I compile the above code with this command: emcc hello.cpp -o hello.js -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -sMODULARIZE=1 -sEXPORT_ES6=1 -sENVIRONMENT=web -lembind -lwebsocket.js

... and here is the corresponding index.html code that I use to test passing and returning a JavaScript string to ContrivedExampleClass::ContrivedExampleFunction(const ContrivedExampleString &):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello Emscripten</title>
</head>
<body>
    <h1>Emscripten C++ to JavaScript</h1>
    <script type="module">
        import createModule from "./hello.js";

        createModule().then((Module) => {
            const ContrivedExampleClass = Module.ContrivedExampleClass;
            const cec = new ContrivedExampleClass;

            var s = cec.ContrivedExampleFunction("ABC")
            console.log("XXX ", s);   // should print "XXX ABC"
        });
    </script>
</body>
</html>

Solution

  • It looks like I found the necessary incantation as part of a post to the emscripten discussion forums: the secret is to call _embed_register_std_string(), like this:

    EMSCRIPTEN_BINDINGS(ContrivedExampleString) { 
        emscripten::internal::_embind_register_std_string(emscripten::internal::TypeID<ContrivedExampleString>::get(), "ContrivedExampleString");
    }
    

    Once I added that, I get the output I wanted from the web browser:

    XXX - "ABC"