内容简介:上篇介绍了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初体验
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web Data Mining
Bing Liu / Springer / 2011-6-26 / CAD 61.50
Web mining aims to discover useful information and knowledge from Web hyperlinks, page contents, and usage data. Although Web mining uses many conventional data mining techniques, it is not purely an ......一起来看看 《Web Data Mining》 这本书的介绍吧!