You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# Get the emsdk repo
git clone https://github.com/emscripten-core/emsdk.git
# Enter that directorycd emsdk
# Fetch the latest version of the emsdk (not needed the first time you clone)
git pull
# Download and install the latest SDK tools.
./emsdk install latest
# Make the "latest" SDK "active" for the current user. (writes .emscripten file)
./emsdk activate latest
# Activate PATH and other environment variables in the current terminalsource ./emsdk_env.sh
开始编译
这里我们以 C++ 库 CppJieba 为例, CppJieba 的 README 中的编译的步骤是
mkdir build
cd build
cmake ..
make
那么我们使用 emsdk 编译为 Webassembly 的步骤就是
mkdir build
cd build
emcmake cmake ..
emmake make
其实不难发现 emsdk 能够完美兼容现有 C/C++ 库的工具链, cmake 替换为 emcmake cmake, make 替换为 emmake make 即可。如果是 gcc 也是可以直接替换为 emcc, 更多见: Building Projects
8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg
EMSCRIPTEN_KEEPALIVE
#defineEMSCRIPTEN_KEEPALIVE __attribute__((used))
EMSCRIPTEN_KEEPALIVE 宏的作用是不要使用 dead code 优化, 虽然 myFunction 它暂时未被用到, 类似于 webpack 中的 Tree Shaking
// test.jsvarModule=require("./build/demo.js");varresult=Module.onRuntimeInitialized=()=>{Module.ccall('myFunction',// name of C function null,// return typenull,// argument typesnull// arguments);}
最近出于业务需求需要, 于是把一个 C++ 库编译为 Webassembly npm 包。主要的考虑是相比于使用 Node.js binding 版本无需在本机进行编译, 这样 Windows 开发的同学与现有的 CI/CD 镜像也不用去搭建编译环境
下载 Emscripten
Emscripten 是将 C/C++ 语言编译为 WebAssembly 的工具, 其他语言参考 Compile a WebAssembly module from…
下面是 macOS 中下载 Emscripten SDK 的步骤, 其他平台参考 Download and install
开始编译
这里我们以 C++ 库 CppJieba 为例, CppJieba 的 README 中的编译的步骤是
mkdir build cd build cmake .. make
那么我们使用 emsdk 编译为 Webassembly 的步骤就是
mkdir build cd build emcmake cmake .. emmake make
其实不难发现 emsdk 能够完美兼容现有 C/C++ 库的工具链, cmake 替换为 emcmake cmake, make 替换为 emmake make 即可。如果是 gcc 也是可以直接替换为 emcc, 更多见: Building Projects
编译结束后就会看到 build 目录生成了如下文件
关于编译后的文件
测试运行
此时我们通过 node test.js 来测试下刚才编译后的产物
结果发现运行报错了
2023-09-01 21:47:44 /Users/github/cppjieba/include/cppjieba/DictTrie.hpp:215 FATAL exp: [ifs.is_open()] false. open /Users/github/cppjieba/dict/jieba.dict.utf8 failed. Aborted(native code called abort())
然后定位 CppJieba 的代码是打开一个本地文件失败了, 具体原因是什么没有打印出来, 是无权限还是什么其他原因?
于是加了如下代码来查看失败的原因
最后打印的错误原因竟然是文件不存在, 人工检查后发现我们本机是有这个文件
文件系统
为什么本机的文件在 wasm 中运行时说找不到了, 让我们先认真看完文档 File systems
如上是 wasm 的文件系统的架构图, wasm 默认使用的 MEMFS, 类似于 Node.js 中的 memfs。
原因是 wasm 为了可移植性, 比如在浏览器中刷新一次页面或者在 Node.js 程序运行停止后利于释放资源, 默认会把资源数据存在内存中。这样能够像启动 docker 一样每次启动做到无状态与隔离
如果你的程序确实需要访问文件资源可以像 docker volumes 一样在启动时挂载到运行时中
比如上面 docker 的实现在 wasm 中就是新增 --preload-file 参数达到同样的效果。如果你仔细观察会发现 wasm 的实现是把挂载目录的文件给序列化到了本机的 build/demo.data 文件中
上面我们说了 wasm 为了可移植性默认使用了 MEMFS, 那如果我的 wasm 代码只需要运行在 Node.js 环境有其他可行办法没有了?
如果不想通过挂载的方式, 那么可以使用 -s NODERAWFS=1 参数来使用 NODERAWFS 文件系统就可以直接访问本机, 那么此时你的 wasm 将只能运行在 Node.js 环境。
同样如果你的 wasm 只需运行在浏览器环境你可以使用 IDBFS, 这样数据都会保存在 IndexedDB 中。更多文件系统见: File System API
到这里我的一点感悟是如果运行 wasm 遇见了一些问题, 不妨先把 wasm 当作一个轻量级的 docker, 也许一下就能想明白为什么这里是这样设计了
导出函数
大多数场景我们需要简单封装一下函数然后给 js 去调用, 比如下面的 myFunction 函数, 需要在函数声明前面加入两个宏
EXTERN
EXTERN 宏的作用是防止 name mangling 类似于 js 的打包中告诉代码丑化插件如 terser 不要去压缩我们的函数名
试想上面的代码被丑化为如下后, 显然在一些场景下会造成问题。所以通常会配置 terserOptions.keep_fnames 为 true
接下来也可以看看使用了 EXTERN 宏与没有使用的区别
编译后查看一下符号链接, 可以发现被 extern 修饰的 ef 与 eg 都保留了原名, 其他如 f、g、h 函数名都变了
EMSCRIPTEN_KEEPALIVE
EMSCRIPTEN_KEEPALIVE 宏的作用是不要使用 dead code 优化, 虽然 myFunction 它暂时未被用到, 类似于 webpack 中的 Tree Shaking
比如 webpack 打包时, 可以通过 PURE 关键字进行声明表示 withAppProvider 函数调用没有副作用, 如果 Button 没有被使用到, 那么 withAppProvider 等也可以放心删除
其他参数
因为 CppJieba 是通过 cmake 进行编译, 所以我们在 CMakeLists.txt 文件中补充了 wasm 编译所需的参数如 -s NODERAWFS=1, 其他参数比如
完善 test.js
最后我们还需在 onRuntimeInitialized 的 callback 中调用 myFunction, Module.onRuntimeInitialized 是 wasm 运行时初始化完成的勾子
发布 npm
现在我们终于完成了编译与运行流程, 接下来就可以在 demo.cpp 中类似于 myFunction 一样封装一下 CppJieba 相关的函数, 最后编译完成就可以把产物发布到 npm 上去了
The text was updated successfully, but these errors were encountered: