Bridging C++ and JavaScript Without Manual Memory Chores
Directly calling a compiled wasm module from JavaScript works, but it’s far from ergonomic. You must manually declare every function signature in a cwrap block, and any use of strings means hand-managing memory buffers. There’s also a deeper issue that makes simple name lookups insufficient for C++ code. embind, part of the Emscripten toolchain, exists to remove those friction points.
const api = {
version: Module.cwrap('version', 'number', []),
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
};
Why Name-Based Calls Fail for C++
The typical cwrap approach relies on a function’s name being intact in the compiled object file. That works for plain C. C++ compilers, however, mangle function names to encode the full signature, since C++ supports overloading — you can have several functions named add with different parameter types. Once the name is mangled, a lookup by its readable string breaks.
Annotating Functions with EMSCRIPTEN_BINDINGS
Instead of marking functions with EMSCRIPTEN_KEEPALIVE and including emscripten.h, you use a new section that explicitly lists what JavaScript should see. For plain functions, the wiring is straightforward:
#include <emscripten/bind.h>
using namespace emscripten;
double add(double a, double b) {
return a + b;
}
std::string exclaim(std::string message) {
return message + "!";
}
EMSCRIPTEN_BINDINGS(my_module) {
function("add", &add);
function("exclaim", &exclaim);
}
Compilation picks up the bindings automatically with an added flag:
$ emcc --bind -O3 add.cpp
The JavaScript side loses all the boilerplate. No cwrap, no memory copies:
<script src="https://web.dev/a.out.js"></script>
<script>
Module.onRuntimeInitialized = _ => {
console.log(Module.add(1, 2.3));
console.log(Module.exclaim("hello world"));
};
</script>
You also get guardrails that wasm errors typically lack, catching type mismatches early rather than surfacing cryptic runtime failures.
Passing Options Objects
JavaScript developers lean on objects for configuration, but translating that pattern to raw wasm is punishing. embind lets you map a C++ struct directly to an object passed from JavaScript using value_object. If an array fits better, value_array works equally well.
#include <emscripten/bind.h>
#include <algorithm>
using namespace emscripten;
struct ProcessMessageOpts {
bool reverse;
bool exclaim;
int repeat;
};
std::string processMessage(std::string message, ProcessMessageOpts opts) {
std::string copy = std::string(message);
if(opts.reverse) {
std::reverse(copy.begin(), copy.end());
}
if(opts.exclaim) {
copy += "!";
}
std::string acc = std::string("");
for(int i = 0; i < opts.repeat; i++) {
acc += copy;
}
return acc;
}
EMSCRIPTEN_BINDINGS(my_module) {
value_object<ProcessMessageOpts>("ProcessMessageOpts")
.field("reverse", &ProcessMessageOpts::reverse)
.field("exclaim", &ProcessMessageOpts::exclaim)
.field("repeat", &ProcessMessageOpts::repeat);
function("processMessage", &processMessage);
}
Once the value type is bound, calling the function reads almost like regular JavaScript API usage:
console.log(Module.processMessage(
"hello world",
{
reverse: false,
exclaim: true,
repeat: 3
}
)); // Prints "hello world!hello world!hello world!"
Exposing Whole Classes
Classes are handled in much the same way. You bind the constructor and its methods within the same block:
#include <emscripten/bind.h>
#include <algorithm>
using namespace emscripten;
class Counter {
public:
int counter;
Counter(int init) :
counter(init) {
}
void increase() {
counter++;
}
int squareCounter() {
return counter * counter;
}
};
EMSCRIPTEN_BINDINGS(my_module) {
class_<Counter>("Counter")
.constructor<int>()
.function("increase", &Counter::increase)
.function("squareCounter", &Counter::squareCounter)
.property("counter", &Counter::counter);
}
On the browser side, the instantiation feels native, blending with ES6 conventions:
<script src="/a.out.js"></script>
<script>
Module.onRuntimeInitialized = _ => {
const c = new Module.Counter(22);
console.log(c.counter); // prints 22
c.increase();
console.log(c.counter); // prints 23
console.log(c.squareCounter()); // prints 529
};
</script>
Working with Plain C Files
embind’s macros require C++, but that doesn’t force a rewrite of existing C libraries. Keep the C and C++ sources in separate groups and tell the build system which files belong to which compiler:
$ emcc --bind -O3 --std=c++11 a_c_file.c another_c_file.c -x c++ your_cpp_file.cpp
Then the C functions are callable through your C++ bindings without issue.
Trade-Offs Worth Knowing
embind makes calls pleasant and debugging saner, but it’s not a zero-cost convenience layer. Both the wasm binary and the JavaScript glue code grow — roughly up to 11 kB after gzip when applied naively. On a tiny module with only a couple of exported functions, that overhead may exceed the value of the bindings.
Further options exist beyond what’s covered here; the embind documentation covers additional types and patterns for deeper integration.



