Menjelajahi jalur C/C++ ke WebAssembly dengan Emscripten: instalasi emsdk, compile hello.c menjadi .html/.js/.wasm, pemakaian EMSCRIPTEN_KEEPALIVE, embind untuk binding class, dan filesystem MEMFS

Setelah di episode 7 kita membangun pipeline Rust + wasm-pack — pada episode ini kita menempuh jalur yang kedua: C/C++ + Emscripten. Emscripten adalah toolchain paling tua dan paling matang untuk kompilasi ke WebAssembly, dan pilihan utama saat kalian ingin me-port kode legacy berbahasa C/C++ — game engine, codec, library scientific — ke browser.
Mengapa Emscripten masih relevan? Karena ekosistem C/C++ sangat besar. Library seperti libjpeg, zlib, ffmpeg, atau Bullet Physics ditulis puluhan tahun lalu, dan Emscripten memungkinkan menggunakannya di web tanpa menulis ulang. Ia juga menyediakan emulasi POSIX yang lengkap — filesystem, threading (pthreads), bahkan OpenGL — yang tidak tersedia di jalur Rust murni.
Emscripten dikelola lewat emsdk (Emscripten SDK manager):
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
emcc --versionemcc --version harus mencetak versi tanpa error. Jangan lupa source ./emsdk_env.sh setiap kali membuka terminal baru, atau tambahkan ke ~/.zshrc.
Note
emsdk tidak perlu diinstall secara global — cukup clone di satu folder, misalnya ~/emsdk, lalu source environment-nya. Install hanya butuh dilakukan sekali; upgrade dilakukan dengan ./emsdk update + ./emsdk install latest lagi.
Buat file hello.c:
#include <stdio.h>
int main() {
printf("Hello dari C! \\n");
return 0;
}Kompilasi dengan Emscripten:
emcc hello.c -o hello.htmlEmscripten menghasilkan tiga file: hello.html (halaman demo), hello.js (glue code), dan hello.wasm (bytecode). Buka hello.html di browser — kalian akan melihat output printf dirender ke console dan layar.
Kenapa ini bisa bekerja? Emscripten memetakan fungsi POSIX (termasuk printf) ke implementasi JavaScript yang ditulis ke terminal virtual. Inilah yang dimaksud emulasi POSIX — kode C yang sama bisa berjalan di Linux dan di browser.
Fungsi C yang ingin dipanggil dari JS perlu di-export. Tanpa dekorasi, compiler akan membuang fungsi yang tidak dipakai dari hasil akhir:
#include <emscripten/emscripten.h>
EMSCRIPTEN_KEEPALIVE
int tambah(int a, int b) {
return a + b;
}
EMSCRIPTEN_KEEPALIVE
int faktorial(int n) {
return n <= 1 ? 1 : n * faktorial(n - 1);
}EMSCRIPTEN_KEEPALIVE memberi tahu compiler: jangan buang fungsi ini, export ke JS. Lalu compile:
emcc math.c -o math.js -s EXPORTED_FUNCTIONS="[_tambah, _faktorial]"Perhatikan prefix underscore: fungsi C tambah muncul sebagai _tambah di sisi JS. Panggil dari Node:
const { _tambah, _faktorial } = require("./math.js")
console.log("tambah:", _tambah(40, 2))
console.log("faktorial:", _faktorial(5))node panggil-math.jstambah: 42
faktorial: 120Memanggil fungsi per fungsi cepat jadi membosankan untuk kode berorientasi objek. Embind otomatis menghasilkan binding untuk class C++:
#include <emscripten/bind.h>
class Lingkaran {
public:
Lingkaran(double r) : radius(r) {}
double luas() const { return 3.14159265 * radius * radius; }
double keliling() const { return 2 * 3.14159265 * radius; }
private:
double radius;
};
EMSCRIPTEN_BINDINGS(geometri) {
class_<Lingkaran>("Lingkaran")
.constructor<double>()
.function("luas", &Lingkaran::luas)
.function("keliling", &Lingkaran::keliling);
}Compile dengan flag embind:
emcc geometri.cpp -o geometri.js --bindDi JS, class C++ dipakai seperti class biasa:
const { Lingkaran } = require("./geometri.js")
const lingkaran = new Lingkaran(7)
console.log("luas:", lingkaran.luas())
console.log("keliling:", lingkaran.keliling())
lingkaran.delete()lingkaran.delete() membebaskan memori yang dialokasikan C++ — penting karena JavaScript garbage collector tidak tahu soal objek C++.
Warning
Objek embind hidup di memori yang dialokasikan Wasm, bukan heap JS. Lupa memanggil .delete() pada objek besar yang sering dibuat akan menyebabkan memory leak yang tidak terdeteksi GC JavaScript.
Emscripten menyediakan sistem file virtual MEMFS — filesystem di dalam memory. Kode C yang membaca/menulis file langsung bekerja:
#include <stdio.h>
int main() {
FILE *f = fopen("/data/hello.txt", "w");
fprintf(f, "data ditulis oleh C");
fclose(f);
char buf[64];
FILE *r = fopen("/data/hello.txt", "r");
fgets(buf, sizeof(buf), r);
fclose(r);
printf("dibaca: %s \\n", buf);
return 0;
}Agar file /data/hello.txt tersedia, gunakan flag --preload-file:
mkdir -p data
echo "file virtual di MEMFS" > data/hello.txt
emcc fs.c -o fs.js --preload-file dataFile data/hello.txt dibundel ke dalam filesystem virtual dan bisa dibaca oleh kode C seolah-olah file nyata. Ini alasan Emscripten sering dipakai untuk game — aset seperti tekstur dan level disimpan via MEMFS.
printf tidak muncul di console — pastikan membuka DevTools; output Emscripten pergi ke console browser, bukan halaman.-s EXPORTED_FUNCTIONS — fungsi C tidak di-export akan dibuang optimizer; gunakan EMSCRIPTEN_KEEPALIVE atau daftar eksplisit._nama_fungsi di sisi JS..delete().--preload-file.Pada episode 8 ini, kalian telah menguasai jalur C/C++ ke WebAssembly: emsdk, emcc, export fungsi, embind, dan filesystem MEMFS.
Inti yang harus dibawa pulang:
emcc) mengkompilasi C/C++ menjadi .html/.js/.wasm dengan emulasi POSIX.EMSCRIPTEN_KEEPALIVE atau EXPORTED_FUNCTIONS diperlukan agar fungsi bisa dipanggil dari JS..delete() objeknya.Di episode 9 selanjutnya kita akan membahas Wasm 3.0 dan fitur-fitur modernnya — WasmGC, exception handling (exnref), tail-call, multi-memory, relaxed SIMD, flexible vectors, dan baseline Wasm 2.0. Sampai jumpa di episode 9!