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>
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"