使用 Emscripten 将 C 和 C++ 编译为 WebAssembly

Web 功能: WebAssembly

使用 Emscripten 将 C 和 C++ 编译为高性能 WebAssembly (Wasm) 模块,以便在浏览器中运行。

1. 现代 Emscripten 编译标志

使用 emcc 或 em++ 进行编译和链接时,请始终使用现代模块化标志:

  • -sEXPORT_ES6:发出标准 ES6 模块语法(import/export),而不是旧版全局对象。这会自动设置 -sMODULARIZE。
  • -sENVIRONMENT=web:剥离 Node.js/shell 运行时代码,以最大限度地减小软件包大小。
  • -sSTRICT:强制执行现代 Emscripten 默认设置,并在遇到已弃用的标志时失败。
  • -sALLOW_MEMORY_GROWTH:允许 WebAssembly 线性内存堆在分配内存时动态扩展。
  • -Oz(或 -O3)+ -flto:在发布 build 中针对最小载荷大小进行优化。

2. 避免阻塞主线程

如果对 WebAssembly 的调用可能需要花费大量时间(即超过几毫秒),则不应在主浏览器线程上调用。

为避免在主浏览器线程上运行此类代码,可以采用以下几种方法:

  1. 在 Web Worker 中运行整个 WebAssembly 程序。这通常是最简单的解决方案,因为它不涉及设置 SharedArrayBuffer 访问权限或更改程序源代码。

  2. 使用 pthread 或 Wasm Worker。这样,工作就可以在多个工作器之间共享,但需要 SharedArrayBuffer 访问权限,这可能涉及添加 COOP/COEP HTTP 标头。

  3. 重构 C/C++ 代码,避免长时间运行的调用。例如,将工作切分成可以从 requestIdleCallback 调用的较小部分。

3. 注意运行时警告和错误

请务必实际测试生成的 WebAssembly 程序,并确保控制台上不会生成任何警告或错误。

在启用 -sASSERTIONS 的情况下构建和运行程序,并修复产生的任何运行时故障。

4. 尽可能避免动态链接

Emscripten 最适合与静态库 (.a) 搭配使用。共享库(.so、.dylib、.dll)在浏览器部署中不太有用,并且可能会使事情变得不必要地复杂。

5. 移植现有 C/C++ 库

将现有 C/C++ 库与标准构建系统集成时:

CMake 项目

使用 emcmake 和 emmake 封装 CMake 命令:

mkdir build && cd build
emcmake cmake .. -DCMAKE_BUILD_TYPE=Release
emmake make

Autoconf/Make 项目

使用 emconfigure 设置交叉编译工具链目标:

emconfigure ./configure --host=wasm32-unknown-emscripten --disable-shared
emmake make