C/C++ con Emscripten
Emscripten: da C/C++ a Wasm
Emscripten è un compilatore che trasforma codice C e C++ in WebAssembly, generando anche il “glue code” JavaScript necessario per l’integrazione nel browser.
Installazione
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh # Linux/macOS
# .\emsdk_env.bat # Windows
Compilazione Base
File add.c:
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
return a + b;
}
EMSCRIPTEN_KEEPALIVE
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
emcc add.c -o add.js -s WASM=1 -s EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]'
Questo genera due file: add.js (glue code) e add.wasm.
Flag Principali di emcc
# Ottimizzazione
emcc file.c -O0 # nessuna ottimizzazione (debug)
emcc file.c -O2 # ottimizzazione bilanciata
emcc file.c -O3 # massima velocità
emcc file.c -Os # ottimizza per dimensione
emcc file.c -Oz # dimensione minima
# Specificare funzioni da esportare
emcc file.c -s EXPORTED_FUNCTIONS='["_add","_factorial"]'
# Dimensione della memoria
emcc file.c -s INITIAL_MEMORY=16777216 # 16 MB
emcc file.c -s ALLOW_MEMORY_GROWTH=1 # crescita dinamica
emcc file.c -s MAXIMUM_MEMORY=1073741824 # max 1 GB
# Modularizzazione
emcc file.c -s MODULARIZE=1 -s EXPORT_NAME="createModule"
# Standalone Wasm (senza glue JS)
emcc file.c -o file.wasm --no-entry
ccall e cwrap
ccall chiama una funzione C direttamente, cwrap crea un wrapper JavaScript riutilizzabile.
// Caricamento del modulo
const Module = await createModule();
// ccall: chiamata diretta
// ccall(nome, tipoRitorno, [tipiParametri], [valoriParametri])
const result = Module.ccall("add", "number", ["number", "number"], [10, 20]);
console.log(result); // 30
// cwrap: crea una funzione JS riutilizzabile
const add = Module.cwrap("add", "number", ["number", "number"]);
const factorial = Module.cwrap("factorial", "number", ["number"]);
console.log(add(3, 4)); // 7
console.log(factorial(10)); // 3628800
I tipi supportati da ccall/cwrap sono: "number", "string", "boolean", null (per void).
Embind: Classi C++ in JavaScript
Embind permette di esporre classi C++ complete a JavaScript con una sintassi elegante.
// vector2d.cpp
#include <emscripten/bind.h>
#include <cmath>
class Vector2D {
public:
float x, y;
Vector2D(float x, float y) : x(x), y(y) {}
float length() const {
return std::sqrt(x * x + y * y);
}
Vector2D add(const Vector2D& other) const {
return Vector2D(x + other.x, y + other.y);
}
float dot(const Vector2D& other) const {
return x * other.x + y * other.y;
}
};
EMSCRIPTEN_BINDINGS(vector_module) {
emscripten::class_<Vector2D>("Vector2D")
.constructor<float, float>()
.property("x", &Vector2D::x)
.property("y", &Vector2D::y)
.function("length", &Vector2D::length)
.function("add", &Vector2D::add)
.function("dot", &Vector2D::dot);
}
emcc vector2d.cpp -o vector2d.js --bind -O2
const Module = await createModule();
const v1 = new Module.Vector2D(3, 4);
console.log(v1.length()); // 5
const v2 = new Module.Vector2D(1, 2);
const v3 = v1.add(v2);
console.log(v3.x, v3.y); // 4, 6
// IMPORTANTE: liberare la memoria manualmente
v1.delete();
v2.delete();
v3.delete();
File System Emulato
Emscripten fornisce un file system virtuale in-memory compatibile con le API POSIX standard.
#include <stdio.h>
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE
void write_file() {
FILE* f = fopen("/data/output.txt", "w");
fprintf(f, "Scritto da Wasm!\n");
fclose(f);
}
EMSCRIPTEN_KEEPALIVE
void read_file() {
char buffer[256];
FILE* f = fopen("/data/output.txt", "r");
fgets(buffer, sizeof(buffer), f);
fclose(f);
printf("Letto: %s", buffer);
}
# Compilare con supporto file system
emcc fs_example.c -o fs_example.js \
-s EXPORTED_RUNTIME_METHODS='["FS"]' \
-s FORCE_FILESYSTEM=1
Da JavaScript puoi anche manipolare il file system emulato:
const Module = await createModule();
// Creare una directory
Module.FS.mkdir("/data");
// Scrivere un file da JS
Module.FS.writeFile("/data/input.txt", "Contenuto da JS");
// Pre-caricare file reali nella build
// emcc ... --preload-file assets/@/assets/
Esempio Completo: Image Processing
#include <emscripten.h>
#include <stdint.h>
EMSCRIPTEN_KEEPALIVE
void grayscale(uint8_t* data, int width, int height) {
for (int i = 0; i < width * height * 4; i += 4) {
uint8_t r = data[i];
uint8_t g = data[i + 1];
uint8_t b = data[i + 2];
uint8_t gray = (uint8_t)(0.299f * r + 0.587f * g + 0.114f * b);
data[i] = data[i + 1] = data[i + 2] = gray;
// data[i + 3] è alpha, rimane invariato
}
}
// Usare con Canvas
const ctx = canvas.getContext("2d");
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// Copiare i pixel nella memoria Wasm
const ptr = Module._malloc(imageData.data.length);
Module.HEAPU8.set(imageData.data, ptr);
// Applicare il filtro
Module._grayscale(ptr, canvas.width, canvas.height);
// Ricopiare i dati nel canvas
imageData.data.set(Module.HEAPU8.subarray(ptr, ptr + imageData.data.length));
ctx.putImageData(imageData, 0, 0);
Module._free(ptr);