00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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);