Webassembly 技术的探索与实践

时间:2021-1-8 作者:admin

long factorial(int num) {
if (num <= 0) return 1;
else {
return num * factorial(num – 1);
}
}

int main () {
int num = factorial(10);
printf(“The Result: %d \n”, num);
}

执行`gcc factorial.c`命令,生成`a.out`文件,执行`./a.out`,输出`The Result: 3628800`, 测试成功。

##### 编译`factorial.c`为 `wasm` 模块

`emcc`命令本身支持多重级别的优化编译选项`(-O0 (no optimization), -O1, -O2, -Os, -Oz, and -O3)`,这里我们使用如下命令:

> emcc -o test.html factorial.c -o3 -s WASM=1

*   通过`-o test.html`指定`Emscripten`生成运行`wasm`模块的`html`文件
*   `-o3`指定优化选项,适合发布构建
*   `-s WASM=1` 指定`Emscripten`输出格式为`wasm`,默认打包为`asm.js`文件

执行后会生成如下文件:

*   `test.html` 编译并实例化test.wasm模块,并在浏览器展示
*   `test.js` 是`C`语言模块与`Javascript/wasm`文件(test.wasm)之间进行转换通信的中间代码
*   `test.wasm` 二进制的`wasm`模块代码

在浏览器中打开`test.html`文件,即能看到展示结果:

![display_wasm](https://example.com/0ed758260aacd42054cc94562e95bba1)

可以看到是一个比较粗糙的展示界面。

#### `wasm` 与 `Javascript` 模块混用

在上面的示例中,我们编写了一个`C`模块,接下来我们希望在 JS 中调用`factorial`方法,想要在浏览器客户端使用`wasm`模块,与 JS 模块一样,我们需要先加载,再执行。

##### 加载 `wasm` 模块

由于`WebAssembly`暂时并不能支持类似于通过`<script type="module">`或者ES6 `import`来声明引入,所以目前的方式是利用Fetch或者Ajax的方法来加载,结合`WebAssembly.instantiate()`API来实例化加载过来的`wasm`二进制代码来实现的。示例如下:

// Fetch
fetch(‘simple.wasm’).then(response =>
response.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, importObject)
).then(results => {
// Do something
});

// Ajax
request = new XMLHttpRequest();
request.open(‘GET’, ‘simple.wasm’);
request.responseType = ‘arraybuffer’;
request.send();

request.onload = function() {
var bytes = request.response;
WebAssembly.instantiate(bytes, importObject).then(results => {
// Do something
});
};

##### 调用 `C/C++` 中的方法

以上只是通过 Fetch API 获取 wasm 文件的方法,想要在 JS 中调用C文件里面的方法,我们需要重新打包下`factorial.c`源文件

> emcc factorial.c -o3 -s WASM=1 -s ONLY_MY_CODE=1 -s EXPORTED_FUNCTIONS="['_factorial']" -o factorial.js

*   -s ONLY_MY_CODE=1 仅仅打包源文件的代码,阻止包含部分Emscripten的标准库
*   -s EXPORTED_FUNCTIONS="['_factorial']" 指定导出方法(注意:这里的方法名称不加下划线会报错)

与上面的打包示例一样, 执行完命令会生成对应的`factorial.wasm`和`factorial.js`文件,这里我们只需要 wasm 文件即可。JS 端完整的调用代码:

// 内存管理
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
// WebAssembly实例对象的环境配置
const importObj = {
‘global’: {},
env: {
abortStackOverflow: () => { throw new Error(‘overflow’); },
table: new WebAssembly.Table({ initial: 0, maximum: 0, element: ‘anyfunc’ }),
tableBase: 0,
memory: memory,
memoryBase: 1024,
STACKTOP: 0,
STACK_MAX: memory.buffer.byteLength,
}
};

var CModule;

fetch(‘factorial.wasm’, { credentials: ‘same-origin’ }).then(res => {
return res.arrayBuffer()
}).then(bytes => {
console.log(‘bytes:’, bytes)
// 利用WebAssembly.instantiate接口将wasm模块的方法与importObject进行映射
return WebAssembly.instantiate(bytes, importObj)
}).then(obj => {
console.log(‘obj:’, obj)
// 执行调用factorial
CModule = obj.instance.exports;
})

function factorial() {
var num = document.getElementById(‘Input’).value;
var val = CModule._factorial(num)
document.getElementById(‘Dispaly’).innerHTML = 结果:${val};
}

html部分代码:

阶乘计算

计算

“`

以上完整的代码示例:请看

其他编写 wasm 的方法

如果你实在不想用C/C++来编写的话,实际上目前有多种编写wasm的方案,可以配合Webpack一起使用。目前我收集了一些方式:

其中Yew支持在 Rust 代码中直接编写 HTML 标签,官方示例的代码是这样的:

“`
html! {
<section class=”todoapp”,>
<header class=”header”,>

{ “todos” }
{ view_input(&model) }

<section class=”main”,>
<input class=”toggle-all”,
type=”checkbox”,
checked=model.is_all_completed(),
onclick=|_| Msg::ToggleAll, />
{ view_entries(&model) }

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。