Ubuntu24 搭建 Emscripten 环境
1. 准备
安装 cmake, python3 和 git 依赖:
// Install Python
sudo apt-get install python3
// Install CMake (optional, only needed for tests and building Binaryen or LLVM)
sudo apt-get install cmake
// Install git
sudo apt-get install git
2. 安装 Emscripten SDK
从 github 上克隆最新的代码到本地:
// Get the emsdk repo
git clone https://github.com/emscripten-core/emsdk.git
这里如果你不想用克隆仓库, 也可以直接去 Github 网站上直接下载对应的源代码压缩包即可.
获取到最新的代码之后, 执行以下命令来安装最新的依赖的工具:
// Enter that directory
cd 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 terminal
source ./emsdk_env.sh
如果一切顺利的话, 安装工作就完成了。
3. Hello World
让我们尝试一个简单的例子, 以确保我们安装的环境能正常工作。
使用 Emscripten 进行编译时有许多选项,但最主要的场景有两个:
- 编译到 Wasm 并创建相应的 HTML 来运行我们的代码,再加上在 Web 环境中运行 Wasm 所需的所有 JavaScript "胶水" 代码。
- 编译到 Wasm, 然后创建 JavaScript.
3.1 创建 HTML 和相应的 JavaScript 代码
- 首先,我们需要一段 c 代码来编译. 例如:
#include <stdio.h>
int main() {
printf("Hello World\n");
return 0;
}
- 解析来在命令行中使用 emsdk 来编译上面的代码:
emcc hello.c -o hello.html
-o html.html
是告诉 Emscripten 生成可以运行我们代码的 HTML 页面, 包括 Javscript 胶水语言和 Wasm 模块.
成功执行上述命令之后, 当前目录中的文件应该看起来像这样子:
hello.wasm
: Wasm 二进制代码模块hello.js
: 包含用于在原生 C 函数和 JavaScript/Wasm 之间翻译的粘合代码hello.html
: 一个用于加载、编译和实例化 Wasm 代码的 HTML 文件,并在浏览器中显示其输出
如何运行该程序
你需要将生成的代码部署到一个 http 服务器上, 然后从一个支持 WebAssembly 的浏览中加载该页面,便可以运行。
不要直接使用本地浏览器打开该 html 文件,这样的话它将不工作,你可能会遇到这个错误: both async and sync fetching of the wasm failed
关于如何搭建一个本地测试 http 服务器,请参考: 如何搭建一个本地测试 http 服务器
3.2 编译 C 代码给 JavaScript 调用
如果你想在 JavaScript
中调用 C
代码中定义的函数,可以使用 Emscripten ccall()
函数和 EMSCRIPTEN_KEEPALIVE
声明,这会将你的函数添加到导出的函数列表中(exported functions)(参考 Why do functions in my C/C++ source code vanish when I compile to WebAssembly).
让我们看看怎么做.
- 创建一个新目录,在目录中创建
hello3.c
, 内容如下:
#include <stdio.h>
#include <emscripten/emscripten.h>
int main() {
printf("Hello World\n");
return 0;
}
#ifdef __cplusplus
#define EXTERN extern "C"
#else
#define EXTERN
#endif
EXTERN EMSCRIPTEN_KEEPALIVE void myFunction(int argc, char ** argv) {
printf("MyFunction Called\n");
}
默认情况下,Emscripten 生成的代码始终只调用 main
函数,其他函数将作为死代码被删除。将 EMSCRIPTEN_KEEPALIVE
放在函数名称之前可以防止这种情况发生。
这里需要导入 emscripten.h
库才能使用 EMSCRIPTEN_KEEPALIVE
。
- 创建
html_template/shell_minimal.html
, 文件内容是{{{ SCRIPT }}}
. - 使用如下命令进行编译。
emcc -o hello3.html hello3.c --shell-file html_template/shell_minimal.html -s NO_EXIT_RUNTIME=1 -s "EXPORTED_RUNTIME_METHODS=['ccall']"
NO_EXIT_RUNTIME=1
: 我们需要这个编译选项,否则当 main
函数退出时, runtime 将会被关闭,我们将无法再调用其他方法.
- 打开你的
hello3.html
文件,并编辑. - 在第一个
<script type="text/javascript">
之前添加一个<button>
元素,如下:
<button id="my-button">Run myFunction</button>
- 在第一个
<script>
元素的末尾添加以下代码:
document.getElementById("my-button").addEventListener("click", () => {
alert("check console");
const result = Module.ccall(
"myFunction", // name of C function
null, // return type
null, // argument types
null, // arguments
);
});
上述代码展示了如何使用 ccall
调用 c 语言中到处的方法.