跳转至

把 C/C++ 程序搬进浏览器 — Emscripten 移植全流程

1. 这一章讲什么

如果说第 6 章是「用新语言为 Wasm 写代码」,这一章就是「把几十年积累的 C/C++ 老代码搬进浏览器」。 动机很实际:游戏引擎、媒体库、科学计算工具、老企业软件——这些代码久经考验,重写一遍又贵又冒险, 编译过去才是正路。本章给出移植的完整工序:编译、依赖、内存、调试、优化、JS 集成,每步都有命令。1

2. 顶层全景

  编译:emcc,三种输出格式按部署场景选
      │
      ▼
  依赖:静态库直接链;动态库转静态;常用库用 Emscripten Ports
      │
      ▼
  内存:Module.HEAP* 视图 + _malloc/_free;对齐、预分配、少搬运
      │
      ▼
  调试:-g 系旗标 + ASSERTIONS + 泄漏检查;四类典型故障对症
      │
      ▼
  优化与集成:-O3/-Os/--closure/ASYNCIFY;Module._xxx 直调 C 函数
      │
      ▼
  走查:SDL 游戏 → Canvas/WebGL;FFT 工具 → Web Audio

图说:六道工序,顺序就是移植的推进顺序;每道都有自己的坑和对应的旗标。

3. 核心原理

3.1 为什么值得移植,难点在哪

书里给的三个收益:近原生速度、跨浏览器可移植、可维护性——继续用现成的 C/C++ 库和工具链, 不用重写。2 三个挑战一一对应后文章节:3

挑战 本章哪节解决
系统级代码要适应沙箱 3.2 输出格式与 3.4 内存模型
依赖与外部库 3.3 依赖管理
Wasm 与 JS 的接口 3.6 集成

3.2 三种输出格式,按场景选

同一份 example.c,emcc 能产出三种形态:4

emcc example.c -o example.html               # 形态一:HTML + JS 胶水 + WASM,开箱即测
emcc example.c -s WASM=1 -o example.js       # 形态二:JS + WASM,嵌进你已有的页面
emcc example.c -s STANDALONE_WASM -o example.wasm   # 形态三:纯 WASM,无胶水,给服务器端用

常用旗标本章新增的四个:-O3(优化)、-s ALLOW_MEMORY_GROWTH=1(允许内存动态增长)、 -s STANDALONE_WASM(独立二进制)、-s WASM=1(明确 Wasm 输出)。5

3.3 依赖管理:三类库三条路

  • 静态库(.a 文件):编译时直接链上去,emcc main.c libmath.a -o output.js;
  • 动态库:没有浏览器里的「动态加载」这回事,转成静态库或编进构建;
  • 第三方库:先查 Emscripten 有没有人移植过。SDL、OpenAL 这类常用库走 Emscripten Ports (官方维护的移植库仓库):emsdk install sdl2 装,emcc -s USE_SDL=2 example.c -o example.js 用。6

3.4 内存:HEAP 视图、malloc、对齐

Emscripten 把线性内存包装成 Module 对象上的一组类型化数组视图: HEAP8(有符号字节)、HEAPU8(无符号字节)、HEAP32、HEAPF32(32 位浮点)…… 全是同一块 buffer 的不同看法。指针就是偏移量。7

数据进出的标准四步(书中原式):8

var ptr = Module._malloc(size);                        // ① 在 Wasm 堆里要一块
Module.HEAPU8.set(new Uint8Array(data), ptr);          // ② 从 JS 写进去
Module._myFunction(ptr, length);                       // ③ 把指针传给 C 函数
Module._free(ptr);                                     // ④ 用完还掉

配套纪律四条:9

  1. 对齐:结构体按自然边界对齐,__attribute__((aligned(4))) 强制;用 #pragma pack 压紧结构体要当心,压出来的非对齐访问会拖慢甚至报错;
  2. 预分配:反复交换的数据,一次分配、反复复用,少碎片少开销;
  3. 少搬运:批处理、共享内存,和第 5 章的 I/O 优化同源;
  4. 内存增长要编译期开:-s ALLOW_MEMORY_GROWTH=1,按 64KB 页长。

3.5 调试:旗标与四类典型故障

调试旗标组合:10

旗标 作用
-g / -g4 带调试符号;-g4 配 -s DEMANGLE_SUPPORT=1 出可读名字的 source map
-s ASSERTIONS=1 运行期断言,抓非法内存访问
-s MALLOC=emmalloc -s EXIT_RUNTIME=1 Emscripten 内存泄漏检查器,程序退出时报告没 free 的块

四类典型故障的症状与对策:11

故障 症状 对策
内存访问越界 莫名崩溃、数据损坏 ASSERTIONS + Memory 页检查
内存泄漏 越来越慢、最终 OOM emmalloc 泄漏检查;手动核对 free
依赖不兼容 编译失败、行为未定义 找 Emscripten 移植版,统一旗标重编
性能瓶颈 卡顿 Performance 页剖析,调优化旗标或重构

3.6 优化与 JS 集成

优化四件:-O3 速度、--closure 1 剥未用代码、-Os 体积、 -s ASYNCIFY 让同步风格的 C 代码能等待异步操作(Emscripten 的独门机制: 把调用栈保存下来、挂起、异步完成后恢复)。12

JS 侧集成就三招:Module._myFunction(a, b) 直调 C 函数;import 里给 C 提供系统调用替身; 异步加载照旧 instantiateStreaming。13

3.7 主线例子:FFT 音频处理器,从 C 库到实时网页

书里两个走查(游戏引擎 + 计算工具),选计算工具这条做主线,因为它的数据流最完整。 (命令与 API 名出自原书;缓冲区的具体长度是演示编的。)

第 1 步:选库。 一个 C 写的 FFT(快速傅里叶变换,把信号从时域变到频域的算法)库。14

第 2 步:编译。 emcc fft.c -O3 -o fft.js——优化拉满,产出 JS 胶水 + wasm。15

第 3 步:接音频。 浏览器采集到一段音频(书里的 Web Audio API 场景), JS 拿到浮点样本数组。16

第 4 步:搬数据(关键一步,书里原式)。 17

var audioBuffer = getAudioBuffer();
var ptr = Module._malloc(audioBuffer.length);       // 要一块 Wasm 堆
Module.HEAPF32.set(audioBuffer, ptr / 4);           // 按 4 字节一格写浮点
Module._processAudio(ptr, audioBuffer.length);      // C 函数就地处理
Module._free(ptr);                                  // 归还

ptr / 4 这个细节值得停一秒:HEAPF32 是「4 字节一个格子」的视图, set(数据, 起始格号) 要的是格号不是字节地址,所以字节指针要除以 4。18

第 5 步:验证。 书里建议与 JS 实现同尺 benchmark(第 5 章的方法论)。19

SDL 游戏那条线同理:emcc main.c -s USE_SDL=2 -o game.js,渲染交给 HTML5 Canvas, Module['canvas'] = document.getElementById('gameCanvas') 把画布递进去, 图形重活走 WebGL 加速。20

4. 作者的判断与证据

  • 命令与旗标:均与 Emscripten 官方文档一致,是本章最硬的部分;
  • 「移植常达到亚毫秒延迟」(结论段):修辞性总结,无实测支撑,属 textbook 定调;
  • 历史叙述("Wasm 2017 年推出"针对 MVP 发布):口径可接受(2015 宣布、2017 首发)。

判断(我们的,不是书里的): 本章是全书最「能照做」的一章,但也漏了移植决策里最关键的一问: 这份 C 代码的「系统调用面」有多大——纯计算库(FFT、压缩、编解码)移植几乎是免费的; 而深度依赖文件系统、进程、原生 GUI 的老程序,沙箱适配的成本可能超过重写。 书里把移植说成了一道 uniform 的工序,实际上它是一道先盘点依赖再决定做不做的工序。 如果错,会错在: 如果 Emscripten 的 POSIX 模拟层(第 2 章提过的那部分)继续膨胀到 「大部分系统调用都能透明模拟」,那「先盘点」的紧迫性确实会下降——但截至本书时间坐标,它仍成立。

5. 边界与局限

  • 多线程移植没展开:pthreads 到 Wasm 线程(SharedArrayBuffer)的映射只字未提,而这是游戏/模拟类移植的常客;
  • 文件系统模拟(Emscripten 的 FS 层)没有讲——大量 C 程序第一步就卡在 fopen 上;
  • ASYNCIFY 的代价(体积膨胀、性能损耗)没量化;
  • 两个「实践例子」是轮廓而非完整工程,大项目会遇到的构建系统改造(CMake 集成)未提。

6. 可带走的

  1. 移植前先盘点系统调用面:纯计算 = 便宜;重系统依赖 = 先评估。
  2. 三种输出格式背下来:测试用 html,嵌页面用 js+WASM,服务器用 STANDALONE_WASM。
  3. 依赖三路线:静态库直接链、动态库转静态、常用库查 Ports(USE_SDL=2 这种)。
  4. Emscripten 的内存语言:Module.HEAPU8/HEAPF32 视图 + _malloc/_free + 指针传参。
  5. HEAPF32.set(buf, ptr / 4)——视图要格号,字节指针除以元素大小。
  6. 对齐 4 字节起步;#pragma pack 慎用;预分配 + 复用。
  7. 调试旗标组合:-g4 -s DEMANGLE_SUPPORT=1、-s ASSERTIONS=1、泄漏检查 MALLOC=emmalloc + EXIT_RUNTIME=1。
  8. 优化四件:-O3、--closure 1、-Os、ASYNCIFY(各自管一样,别混用目的)。
  9. 性能claim要和 JS 同尺对比才算数。
  10. 渲染类移植:Canvas 递进去(Module['canvas']),图形走 WebGL。

7. 原文地图

主题 原书章 原文位置
移植动机与三大挑战 Porting C/C++ Applications to Web-Assembly text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:69(搜「reuse mature, optimized codebases」) · text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:89(搜「Adapting system-level code」)
三种输出格式 同上 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:145(搜「HTML + JS + WASM」) · text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:177(搜「STANDALONE_WASM」)
编译旗标 同上 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:189(搜「ALLOW_MEMORY_GROWTH」)
依赖管理三路线 同上 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:253(搜「Static Libraries」) · text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:269(搜「emsdk install sdl2」)
HEAP 视图与指针四步 同上 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:285(搜「HEAP8, HEAP32」) · text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:297(搜「Module._malloc」)
对齐与预分配 同上 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:317(搜「aligned(4)」) · text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:347(搜「Pre-allocate Buffers」)
调试旗标与泄漏检查 同上 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:405(搜「DEMANGLE_SUPPORT」) · text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:433(搜「MALLOC=emmalloc」)
四类典型故障 同上 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:419(搜「Memory Access Violations」)
优化与 ASYNCIFY 同上 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:511(搜「--closure 1」) · text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:531(搜「ASYNCIFY」)
JS 集成三招 同上 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:541(搜「Module._myFunction」)
游戏引擎与 FFT 走查 同上 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:565(搜「USE_SDL=2」) · text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:595(搜「fft.c -O3」) · text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:613(搜「HEAPF32.set」)

  1. 出处:「Porting C/C++ Applications to Web-Assembly」第 51–95 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:69,搜「reuse mature, optimized codebases」)。 ↩

  2. 出处:「Porting C/C++ Applications to Web-Assembly」第 71–85 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:73,搜「Performance:」)。三个收益为 Performance、Portability、Maintainability。 ↩

  3. 出处:「Porting C/C++ Applications to Web-Assembly」第 87–93 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:89,搜「sandboxed browser environment」)。 ↩

  4. 出处:「Porting C/C++ Applications to Web-Assembly」第 139–177 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:147,搜「HTML file along with JavaScript glue code」)与 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:157(搜「-s WASM=1 -o example.js」)、text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:177(搜「-s STANDALONE_WASM」)。 ↩

  5. 出处:「Porting C/C++ Applications to Web-Assembly」第 179–199 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:181,搜「-s WASM=1」)。 ↩

  6. 出处:「Porting C/C++ Applications to Web-Assembly」第 253–271 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:257,搜「libmath.a」)与 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:269(搜「emsdk install sdl2」)。 ↩

  7. 出处:「Porting C/C++ Applications to Web-Assembly」第 283–289 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:285,搜「HEAP8, HEAP32」)。 ↩

  8. 出处:「Porting C/C++ Applications to Web-Assembly」第 295–299 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:297,搜「Module._malloc(size)」)。 ↩

  9. 出处:「Porting C/C++ Applications to Web-Assembly」第 309–351 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:317,搜「aligned(4)」)。 ↩

  10. 出处:「Porting C/C++ Applications to Web-Assembly」第 401–435 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:405,搜「DEMANGLE_SUPPORT」)与 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:433(搜「MALLOC=emmalloc」)。 ↩

  11. 出处:「Porting C/C++ Applications to Web-Assembly」第 417–447 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:419,搜「Memory Access Violations」)。 ↩

  12. 出处:「Porting C/C++ Applications to Web-Assembly」第 493–531 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:511,搜「--closure 1」)与 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:529(搜「ASYNCIFY」)。 ↩

  13. 出处:「Porting C/C++ Applications to Web-Assembly」第 533–555 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:541,搜「Module._myFunction」)。 ↩

  14. 出处:「Porting C/C++ Applications to Web-Assembly」第 589–605 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:591,搜「FFT or matrix solver」)。 ↩

  15. 出处:「Porting C/C++ Applications to Web-Assembly」第 593–595 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:595,搜「fft.c -O3」)。 ↩

  16. 出处:「Porting C/C++ Applications to Web-Assembly」第 599–605 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:601,搜「Web Audio API」)。 ↩

  17. 出处:「Porting C/C++ Applications to Web-Assembly」第 607–617 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:609,搜「getAudioBuffer」)。 ↩

  18. 出处:「Porting C/C++ Applications to Web-Assembly」第 613 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:613,搜「HEAPF32.set」)。「格号 vs 字节地址」是按 HEAPF32 为 4 字节元素视图的定义推出的解释。 ↩

  19. 出处:「Porting C/C++ Applications to Web-Assembly」第 597 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:597,搜「Benchmark performance against JavaScript」)。 ↩

  20. 出处:「Porting C/C++ Applications to Web-Assembly」第 559–587 段(text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:565,搜「-s USE_SDL=2」)与 text/18-fm-ultimate-webassembly-for-high-performance-apps-b.txt:579(搜「Module['canvas']」)。 ↩