C and C++ with Emscripten

15.1 What Emscripten Provides

A complete toolchain (Clang/LLVM + Binaryen) plus runtime support: libc (musl-based), libc++, a virtual filesystem, SDL2/GLFW/OpenGL ES → WebGL, pthreads → Web Workers, networking emulation, and a JS glue file that loads and connects everything.

15.2 Hello World and Calling C from JS

// math.c
#include <emscripten/emscripten.h>
#include <stdio.h>

EMSCRIPTEN_KEEPALIVE
int fib(int n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }

EMSCRIPTEN_KEEPALIVE
double average(const double* values, int len) {
    double sum = 0;
    for (int i = 0; i < len; i++) sum += values[i];
    return len ? sum / len : 0;
}

int main(void) {
    printf("Hello from C!\n");   // goes to console.log
    return 0;
}
emcc math.c -O3 -o math.js \
  -sEXPORTED_FUNCTIONS=_fib,_average,_malloc,_free \
  -sEXPORTED_RUNTIME_METHODS=ccall,cwrap,HEAPF64 \
  -sMODULARIZE -sEXPORT_ES6 -sENVIRONMENT=web
emcc math.c -o index.html        # generates an HTML test page
import createModule from "./math.js";
const Module = await createModule();
console.log(Module._fib(30));
const fib = Module.cwrap("fib", "number", ["number"]);
console.log(fib(20));

// Passing an array
const values = new Float64Array([1, 2, 3, 4]);
const ptr = Module._malloc(values.byteLength);
Module.HEAPF64.set(values, ptr / 8);
console.log(Module._average(ptr, values.length));
Module._free(ptr);

// Strings: Module.ccall("greet", "string", ["string"], ["Ana"])

15.3 Embind (C++ Classes to JS)

Alternative: WebIDL Binder. TypeScript definitions: --emit-tsd.

// shapes.cpp
#include <emscripten/bind.h>
#include <string>
#include <vector>
using namespace emscripten;

class Counter {
public:
    explicit Counter(int start) : value(start) {}
    void increment() { ++value; }
    int get() const { return value; }
    std::string label() const { return "Counter=" + std::to_string(value); }
private:
    int value;
};

double sum(const std::vector<double>& v) { double s = 0; for (double x : v) s += x; return s; }

EMSCRIPTEN_BINDINGS(my_module) {
    class_<Counter>("Counter")
        .constructor<int>()
        .function("increment", &Counter::increment)
        .function("get", &Counter::get)
        .property("label", &Counter::label);
    register_vector<double>("VectorDouble");
    function("sum", &sum);
}
em++ shapes.cpp -O2 -lembind -o shapes.js -sMODULARIZE -sEXPORT_ES6
const M = await createModule();
const c = new M.Counter(5);
c.increment();
console.log(c.get(), c.label);
c.delete();                        // ⚠️ free C++ objects manually (or use FinalizationRegistry carefully)

15.4 Key Emscripten Features and Flags

Flag / feature -O0 … -O3, -Os, -Oz -sMODULARIZE -sEXPORT_ES6 Export a factory function / ES module

-sINITIAL_MEMORY=64MB, -sMAXIMUM_MEMORY Minimal JS dependency (WASI-like imports) -pthread -sPTHREAD_POOL_SIZE=4

Threads via Web Workers (needs cross-origin isolation) Native Wasm exception handling for C++ -sASYNCIFY, -sJSPI

Call async JS from synchronous C code (Module 28) --preload-file assets@/ / --embed-file Virtual filesystem contents

-sFORCE_FILESYSTEM, -lidbfs.js, WasmFS Filesystems (MEMFS, IDBFS persistence, NODEFS, OPFS via WasmFS)

-sUSE_SDL=2, -sUSE_WEBGL2=1, -sFULL_ES3 -sUSE_ZLIB=1, -sUSE_LIBPNG=1 (emports) -gsource-map, -g -sASSERTIONS, -fsanitize=address Runtime checks, ASan Minify the JS glue

emmake make, emcmake cmake, emconfigure Build existing projects

EM_JS, EM_ASM, emscripten_run_script Inline JS from C Browser-friendly main loop (requestAnimationFrame)

#include <emscripten.h>
EM_JS(void, show_alert, (const char* msg), {
  alert(UTF8ToString(msg));
});

15.5 Porting an Existing Library

Common issues: blocking main loops (use emscripten_set_main_loop or Asyncify), synchronous file/network I/O, threads without COOP/COEP headers, inline assembly/CPU intrinsics (replace with SIMD intrinsics via -msimd128 and wasm_simd128.h), and 64-bit assumptions.

emcmake cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
emcc build/libfoo.a glue.c -O3 -o foo.js -sMODULARIZE [email protected]