Web Assembly 构建 C++ 代码在 JavaScript 环境调用
Emscripten 环境准备
- 拉取核心驱动脚本
bash
# Get the emsdk repo
git clone https: //github.com/emscripten-core/emsdk.git
# Enter that directory
cd emsdk
- 获取 Emscripten 最新版工具并配置环境
bash
# 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
工具测试
在目录 emsdk/test/
下新建一个 test.c 文件,内容如下:
c
#include <stdio.h>
int main() {
printf("hello, FengCh!\n");
return 0;
}
在终端中运行代码 emcc test/test.c -o test.wasm.js
,最终在目录下生成一个 test.wasm.js
js 文件和 test.wasm.wasm
的二进制文件:
通过 node 运行 test.wasm.js
文件,终端输出"hello,FengCh":
demo
文件目录:
python
.
├── compile # 编译目录
│ ├── build_wasm.py # C++ 编译脚本
│ ├── c++ # C++ 代码
│ │ └── communication.cpp
│ └── js # js 代码
│ └── utils.js
└── src
└── main.js
下面 C++ 代码中有一个 Communication
类,类里面有两个方法, 其中 executeCb
方法内编写了 js 代码并输出一段信息,且最终编译后会在 js 层调用,monitorCb
方法在编译后由 js 层调用,并传入一个回调函数和一个 isExecute
参数,最终通过 EMSCRIPTEN_BINDINGS
将 C++ 环境的类与成员函数绑定到 JavaScript 环境中:
cpp
#include <emscripten.h>
#include <emscripten/bind.h>
class Communication {
public:
void executeCb() {
EM_ASM_({
console.log('这是 C++ 层的函数在 js 层被调用,且在 C++ 内嵌套 js 代码');
});
}
void monitorCb(emscripten::val cb,bool isExecute){
if(isExecute){
cb();
};
}
};
EMSCRIPTEN_BINDINGS(communication_module_bindings) {
emscripten::class_<Communication>("Communication")
.constructor<>()
.function("executeCb", &Communication::executeCb)
.function("monitorCb", &Communication::monitorCb);
}
编写如下 Python 脚本编译 C++ 代码,其中 source_wasm_build_path
字段包含了要编译的 C++ 代码路径,emcc_wasm_build_args
字段指定了 Emscripten 编译参数,最终根据编译结果分别输出对应信息:
py
import os
source_wasm_build_path = ' \./../compile/c++/communication.cpp'
emcc_wasm_build_args = ' \
--post-js ./js/utils.js \
-o ../src/communication.wasm.js'
command_wasm_build = 'emcc -lembind -s WASM=1' + source_wasm_build_path + emcc_wasm_build_args
result = os.system(command_wasm_build)
print(command_wasm_build)
if result == 0:
print('Build wasm success')
else:
print('Build wasm failed')
exit(1)
进入 compile 目录下终端输入命令 python build_wasm.py
并运行:
编译成功后在 src 目录下多出 编译后的 js 文件和 wasm 二进制文件:
在 main.js 中可以通过引入生成的胶水文件 communication.wasm.js 在 Node 环境下调用 C++ 层的代码,代码如下:
js
const Module = require("./communication.wasm.js");
// Wasm模块加载并初始化完成后被调用的回调函数
Module.onRuntimeInitialized = (_) => {
const communicateModule = new Module.Communication();
// 调用 C++ 层的函数
communicateModule.executeCb();
// js 层的函数在 C++ 层被调用
communicateModule.monitorCb(() => {
console.log("这是 js 层的函数在 C++ 层被调用");
}, true);
// 编译 C++ 代码时在末尾插入的额外的 js 代码
const result = Module.safelyParse("{name: 'icebergfeng'}");
console.log(result);
};
在 compile 目录执行 node ../src/main.js
得到结果如下: