内容简介:上篇介绍了WebAssembly的前生今世,这篇准备写几个例子试玩一下。首先肯定是先安装Emscripten,这是一个toolchain,可以把C/C++代码编译成asm.js和WASM字节码。内部其实分为三部分:写个HelloWorld:
上篇介绍了WebAssembly的前生今世,这篇准备写几个例子试玩一下。
1.安装Emscripten
首先肯定是先安装Emscripten,这是一个toolchain,可以把C/C++代码编译成asm.js和WASM字节码。内部其实分为三部分:
- 调用编译器前端Clang把C/C++代码编译成LLVM字节码
- 调用编译器后端Fastcomp把LLVM字节码编译成asm.js
- 调用Binaryen的asm2wasm把asm.js转换成WASM字节码
Emscripten的具体安装步骤如下:
# 下载Emscripten源码 git clone https://github.com/juj/emsdk.git cd emsdk # 安装最新版本 ./emsdk install latest # 使用最新版本 ./emsdk activate latest # 添加环境变量 source ./emsdk_env.sh
2.HelloWorld
写个HelloWorld:
#include <stdio.h> int main(int argc, char** argv) { printf("Hello World!\n"); return 0; }
用下面的命令编译:
注:最新版本的Emscripten默认就会生成.wasm,不需要像以前那样指定“-s WASM=1”了
emcc hello.c -o index.html
编译后的目录文件结构如下:
需要注意的是,生成的index.html直接双击打开是无法运行的,必须运行一个本地的HTTP服务器。通过下面的命令安装http-server:
npm install http-server -g
然后,在当前目录下直接运行HTTP服务器:
最后通过URL访问该网页: http://127.0.0.1:8080/index.html
当然,如果你想把网页搞得更漂亮一点,还可以自定义HTML模版,在emsdk目录中有一个最小版本的HTML模版shell_minimal.html,可以在这个基础上进行修改,然后用下面的命令编译:
emcc hello.c -o index.html --shell-file shell_minimal.html
3.C代码调用Javascript
C代码里可以通过Emscripten中的 EM_ASM 这个宏调用Javascript代码,注意Javascript代码要放进大括号里。我们把上面的代码改一改,让它蹦一个弹窗:
#include <stdio.h> #include <emscripten.h> int main(int argc, char** argv) { EM_ASM({ alert("Hello!"); }); return 0; }
重新编译、运行的结果如下:
如果运行后发现代码修改没有生效,清理一下浏览器的缓存再重新加载。
4.Javascript调用C代码
Javascript也可以直接调用WebAssembly中定义的函数,需要使用Emscripten中的 ccall()函数 以及 EMSCRIPTEN_KEEPALIVE声明 (将你的函数添加到导出的函数列表中,默认只导出main())。
我们在前面的代码中增加一个自定义函数:
#include <stdio.h> #include <emscripten.h> void EMSCRIPTEN_KEEPALIVE myFunction() { printf("Good Morning!\n"); } int main(int argc, char** argv) { printf("Hello World!\n"); return 0; }
编译的时候要注意,由于ccall()函数默认是不导出的,需要加上一个编译选项:
emcc hello.c -o index.html -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall"]'
然后在生成的index.html中添加一个按钮,用来调用我们的自定义函数:
<button class="mybutton">Run myFunction</button>
最后在</script>标签之前添加以下代码,响应按钮点击事件:
document.querySelector('.mybutton').addEventListener('click', function(){ var result = Module.ccall('myFunction', // name of C function null, // return type null, // argument types null); // arguments });
最终运行结果:
5.手写WebAssembly代码
如果你觉得不过瘾,或者觉得自动生成的wasm代码不够好,可以尝试手写WebAssembly代码。
我们可以先写一个文本格式的WebAssembly模块,一般以.wat或者.wast作为后缀名,然后通过 工具 把它转换成.wasm文件。比如我们实现一个计算阶乘的函数,保存成fac.wat:
(func (export "fac") (param $x i32) (result i32) (if (result i32) (i32.eq (get_local $x) (i32.const 0)) (then (i32.const 1)) (else (i32.mul (get_local $x) (call 0 (i32.sub (get_local $x) (i32.const 1)))) ) ) )
然后我们需要使用wabt工具进行转换,首先安装wabt:
git clone --recursive https://github.com/WebAssembly/wabt cd wabt make
编译完成后会在bin目录中的生成一堆工具:
- wat2wasm:把.wat转换成.wasm格式
- wasm2wat:把.wasm转换成.wat格式
- wasm-objdump:类似于objdump,查看wasm汇编指令
- wasm-interp:一个基于栈的解释器,可以用来做执行验证
- wasm2c:把.wasm转成C代码
- wat-desugar:“去糖”工具,通过wasm2c生成的C代码是跟汇编代码1:1对应的,比较难懂,可通过该工具转换成优化之前的代码结构
我们先试试 wat2wasm 工具:
wat2wasm fac.wat -o fac.wasm -v
会生成fac.wasm文件,同时因为加了-v选项,会在控制台上打印出对应的WASM指令。
我们可以再通过 wasm2wat 把.wasm转回.wat:
wasm2wat fac.wasm -o fac-flat.wat
打开生成的代码,你会发现跟之前的代码似乎略有不同:
(module (type (;0;) (func (param i32) (result i32))) (func (;0;) (type 0) (param i32) (result i32) get_local 0 i32.const 0 i32.eq if (result i32) ;; label = @1 i32.const 1 else get_local 0 get_local 0 i32.const 1 i32.sub call 0 i32.mul end) (export "fac" (func 0)))
这种格式被称为“flat”格式,是针对基于栈的虚拟机的,你可以发现它跟之前控制台上打印的指令其实是1:1对应的。如果想还原成方便我们阅读的“folded”格式,则需使用 wat-desugar 工具:
wat-desugar fac-flat.wat --fold -o fac-folded.wat
查看新生成的fac-folded.wat,会发现就跟我们的原始代码相差无几了。
如何验证我们生成的WASM代码是否运行正常呢?可以通过 wasm-interp 工具。但是这个工具似乎不能接收参数,因此需要额外写一个无参数的函数调用我们的fac()函数:
(func $fac (param $x i32) (result i32) (if (result i32) (i32.eq (get_local $x) (i32.const 0)) (then (i32.const 1)) (else (i32.mul (get_local $x) (call 0 (i32.sub (get_local $x) (i32.const 1)))) ) ) ) (func (export "exported_fac") (result i32) i32.const 5 call $fac )
先通过wat2wasm转换成fac2.wasm,然后通过wasm-interp解释执行WASM代码:
wasm-interp fac2.wasm --run-all-exports 运行结果:exported_fac() => i32:120
最后,我们再尝试一下用 wasm2c 工具把.wasm文件转换成C代码:
wasm2c fac.wasm -o fac.c
转换完会生成一个fac.h和一个fac.c,fac.h里导出了2个函数:
extern void WASM_RT_ADD_PREFIX(init)(void); extern u32 (*WASM_RT_ADD_PREFIX(Z_facZ_ii))(u32);
第一个init()函数会帮我们做一些注册和初始化的工作,另外一个fac()函数就是和我们刚刚看到的flat格式的WASM代码1:1对应的C代码实现了。
6.Javascript和WebAssembly互相调用
首先介绍一下WebAssembly中的四大组件:
- 模块(module):已经被编译为可执行机器码的WebAssembly二进制代码
- 实例(instance):模块的一个实例化对象,一个模块可以有多个实例
- 内存(Memory):一个可变大小的ArrayBuffer,无具体类型
- 表格(Table):一个可变大小的包含引用类型(如函数)的带类型数组
模块 & 实例
Javascript要执行WebAssembly代码,首先需要下载.wasm文件,然后调用WebAssembly.instantiate()进行编译,生成模块和实例。一般会封装成以下函数方便代码复用:
function fetchAndInstantiate(url, importObject) { return fetch(url).then(response => response.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes, importObject) ).then(results => results.instance ); }
返回的result中包含了module和instance两个对象,一般直接使用instance即可。如果有多个地方需要创建实例,可以考虑把module缓存起来,后面直接实例化,可以避免重复编译。
对象导入导出
上面的函数里有个importObject参数,可以把Javascript中的对象传递到WASM中,比如传递一个函数对象:
var importObject = { imports: { imported_func: function(arg) { console.log(arg); } } };
然后WASM中用下面的方式声明一下,就可以使用了:
(module (func $i (import "imports" "imported_func") (param i32)) (func (export "exported_func") i32.const 42 call $i))
上面的代码还导出了一个函数exported_func,会保存在instance.exports中,在Javascript中可以通过以下方式调用:
fetchAndInstantiate("XXX.wasm", importObject).then(function(instance){ instance.exports.exported_func(); })
内存共享
官方有一个示例来说明如何在Javascript和WASM之间共享内存:
var i32 = new Uint32Array(results.instance.exports.mem.buffer); for (var i = 0; i < 10; i++) { i32[i] = i; } var sum = results.instance.exports.accumulate(0, 10); console.log(sum);
首先为instance.exports.mem.buffer对象创建了一个Uint32Array视图,方便赋值。然后调用WASM模块导出的accumulate()函数:
重点是标红的那两句,首先声明导入内存对象,然后通过i32.load每次从内存中读取4个字节。
表格示例
表格是用来传递带类型的对象引用的,但是目前阶段只支持传递函数对象引用。比如下面的代码导出了一个表格,包含两个函数引用:
(module (func $thirteen (result i32) (i32.const 13)) (func $fourtytwo (result i32) (i32.const 42)) (table (export "tbl") anyfunc (elem $thirteen $fourtytwo)) )
Javascript里用下面的方式可以调用表格中的WASM函数:
var tbl = results.instance.exports.tbl; console.log(tbl.get(0)()); // 13 console.log(tbl.get(1)()); // 42
最后推荐几个WebAssembly必备的网站,大部分问题基本都能在上面找到答案,总有一款适合你:
-
WebAssembly官网: http://webassembly.org.cn/docs/js/
-
mozilla开发者: https://developer.mozilla.org/zh-CN/docs/WebAssembly
-
emscripten官网: https://kripken.github.io/emscripten-site/docs/
-
WebAssembly开发者指南: http://www.clipcode.net/training/clipcode-webassembly-devguide.pdf
参考:
http://kripken.github.io/emscripten-site/docs
https://www.cnblogs.com/saintlas/p/5738739.html
https://developer.mozilla.org/zh-CN/docs/WebAssembly/C_to_wasm
https://github.com/webassembly/wabt
更多文章欢迎关注“鑫鑫点灯”专栏: https://blog.csdn.net/turkeycock
以上所述就是小编给大家介绍的《WebAssembly初体验》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 降低云游戏延迟优化云游戏体验:贝塞斯达推出Orion技术,还公布了免费体验计划
- PyTorch 初体验
- indexedDB 初体验
- golang爬虫初体验
- Netty 入门初体验
- Ansible初体验
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
一本书读懂大数据
黄颖 / 吉林出版集团有限责任公司 / 2014-12-1
进入大数据时代,让数据开口说话将成为司空见惯的事情,书中将从大数据时代的前因后果讲起,全面分析大数据时代的特征、企业实践的案例、大数据的发展方向、未来的机遇和挑战等内容,展现一个客观立体、自由开放的大数据时代。一起来看看 《一本书读懂大数据》 这本书的介绍吧!