简要记录如何使用 Emscripten 把 C++ 编译成 WebAssembly。
虽然 WebAssembly 还处在发展中阶段,但是已经可以提前玩起来了。把 C++ 编译成 WASM 需要 Emscripten 编译器。
安装
需要 Git 和良好的网络环境。
非 Windows:
1 | $ git clone https://github.com/emscripten-core/emsdk.git |
Windows:
1 | > git clone https://github.com/emscripten-core/emsdk.git |
为了方便也可以顺带配一下 PATH 环境变量。
1 | C:\path\to\emsdk |
如果不用 emsdk_env 初始化环境变量而是手动配置,Python 所在的目录也需要在 PATH 变量中。
C++ 胶水
例子
Emscripten 提供了 Embind 来绑定 C++ 的函数和类到 JavaScript 对象,写起来更自然,类似 Node.js 的 NAPI。
使用这个特性时必须加上链接器选项 --bind
。
原生代码:
1 | // add.cpp |
编译链接生成 js 和 wasm 文件:
1 | $ em++ -std=c++11 -s DISABLE_EXCEPTION_CATCHING=0 -s ALLOW_MEMORY_GROWTH=1 -O3 --bind -o add.js add.cpp |
gcc 的参数基本都可以用,这里的 -s
是 Emscripten 额外的选项,DISABLE_EXCEPTION_CATCHING=0
可以正常 catch 到 C++ 异常,ALLOW_MEMORY_GROWTH=1
可以让 WebAssembly 内存超出初始化的大小时自动开辟新内存。
文档在这里。
JS 调用:
1 |
|
默认输出的 JS 也支持 Node.js 运行环境,但是最好不要直接用在 Webpack 里,因为它里面用到了很多 Node.js 变量,Webpack 会自动导入 Node Polyfill 导致生成的包体积超大。
1 | const Module = require('./add.js') |
如果需要输出 ES6 模块格式的 JS,需要指定 -o add.mjs
,然后这样用
1 | import main from './add.mjs' |
ES6 模块可以用在 Webpack 里,但是要注意 import.meta.url
的处理。
类型映射
上面 add
函数用到的类型 int
,Embind 可以自动映射成 JS 的 number
类型,用 TypeScript 声明来描述的话相当于:
1 | export declare function add (a: number, b: number): number |
也就是说 JS 调用的时候可以传 number
类型进来,如果传别的类型就会抛错。
下表是 Embind 支持的类型映射:
C++ 类型 | JavaScript 类型 |
---|---|
void |
undefined |
bool |
boolean |
char |
number |
signed char |
number |
unsigned char |
number |
short |
number |
unsigned short |
number |
int |
number |
unsigned int |
number |
long |
number |
unsigned long |
number |
float |
number |
double |
number |
std::string |
string | ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array |
std::wstring |
string (utf-16) |
emscripten::val |
any |
值得注意的是 emscripten::val
这个类,定义在 <emscripten/val.h>
里面,它可以映射成任意 JS 类型,相当于是 NAPI 的 Napi::Value
,很好用,可以用它来直接操作 JS 对象。
比如这样用:
1 |
|
等价于:
1 | function stringify (jsobj: any): string { |
用 CMake 构建
非 Windows:
1 | $ mkdir -p ./build |
Windows:
需要安装 Make for Windows 跑 CMake 生成的 Makefile。
1 | > mkdir build |
调试
不要用 -O
参数,加上 -g4 --source-map-base http://<host>:<port>/
,host 和 port 自己填。把 map 放在正确的位置即可在浏览器开发者工具中给 C++ 代码打断点调试。