title: [WebAssembly 入门] Hello, world!
date: 2018-3-29 14:45:00
categories: WebAssembly, 笔记
tags: WebAssembly, JavaScript, Rust, LLVM toolchain
auther: Yiniau
[WebAssembly 入门] Hello, world!
进过一段时间的基础知识学习,是时候正式开始WebAssembly编程了!
我花了2个月左右的时间入门Rust,拿它写了一个sudoku游戏。 因此选择 Rust
-> LLVM toolchain
-> WebAssembly
对我而言是比较自然的。
准备
第一 通过 Cargo 新建一个 lib 项目
cargo new hello_world --lib && cd hello_world
复制代码
第二 需要一个HTML承载JS代码并显示效果
vim index.html
复制代码
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebAssemblytitle>
head>
<body>
<script src="main.js">script>
body>
html>
复制代码
第三 创建JS文件
vim main.js
复制代码
正式开始探险!
开始之前。确认一下现在的目录情况
ll .
-rw-r--r-- 1 yiniau staff 52B Mar 25 22:14 Cargo.lock
-rw-r--r-- 1 yiniau staff 112B Mar 25 22:14 Cargo.toml
-rw-r--r-- 1 yiniau staff 165B Mar 29 15:00 index.html
-rw-r--r-- 1 yiniau staff 743B Mar 29 15:00 main.js
drwxr-xr-x 3 yiniau staff 96B Mar 29 14:58 src
ll src
-rw-r--r-- 1 yiniau staff 494B Mar 29 14:35 lib.rs
复制代码
通过之前的几篇翻译, 我们知道能够通过 fetch
获取.wasm文件并通过转换成类型化数组或数组缓冲区并传入 WebAssembly.instantiate
进行编译 但是我们还没有一个.wasm文件,当务之急是在lib.rs中编辑Rust代码并通过 rustc
编译输出 .wasm 文件
rustup 工具链请自行搜索安装,十分简单。
千军万马,注释先行
//! hello world with WebAssembly
复制代码
作为开始的一小步,先从最简单的开始————在WebAssembly中调用JS传递过来的函数及其闭包,主要的逻辑在JS函数中封装好
声明imports导入资源
我们可以通过 在imports导入资源中添加环境属性来实现,先来创建一个imports对象
const imports = {
env: {}
}
复制代码
添加函数/闭包
在env
属性中添加想要传递的函数
env: {
function hello_world() {
const h1 = document.createElement('h1');
h1.innerHTML = 'Hello, world';
const body = document.querySelector('body');
body.appendChild(h1);
}
}
复制代码
然后让我们转战 lib.rs
声明外部函数
要使用JS的函数,我们需要利用ffi,先在extern中声明外部函数
//! a WebAssembly module with Rust
extern {
fn hello_world();
}
复制代码
这下就能够在函数体中调用了
WebAssembly函数体
实现js中调用的函数接口
/// call js function to access DOM
#[no_mangle]
pub extern fn hello_call_js() { // equal pub extern "C" fn ...
unsafe {
hello_insert_dom();
}
}
复制代码
ok, 目前lib.rs
应该长这样了
//! a WebAssembly module with Rust
extern {
fn hello_insert_dom();
}
/// return "Hello, world"'s bytes array
#[no_mangle]
pub extern fn hello_call_js() { // equal pub extern "C" fn ...
unsafe {
hello_insert_dom();
}
}
复制代码
到此,rust的准备工作已经就绪,下一步就是编译了
编译
在zsh中输入
mkdir build
rustc +nightly --target=wasm32-unknown-unknown -O --crate-type=cdylib src/lib.rs -o build/hello.wasm
# +nightly 指明使用nightly版本
# --target 指定编译器后
# -O == -C opt-level=2
# -C opt-level=2 指定优化级别为2,范围是 0-3
# --create-type=cdylib 添加一个由编译器接受的crate类型,称为cdylib,它对应于从Rust动态库中导出的C接口。
# -o 指定输出文件名
复制代码
// TODO:记录第一个问题,动态库是啥,使用这个库有什么用。
DONE!!
我们可以在 build/
目录中找到 hello.wasm 文件
如果想看看长什么样子,可以用
hexdump
查看
在JS中导入.wasm文件并使用
fetch('build/hello.wasm') // 获取到的是以16进制表达的二进制文件
.then(res => res.arrayBuffer()) // 放入数组缓冲区
.then(bytes => WebAssembly.instantiate(bytes, imports)) // 将二进制数据传入,交给JIT处理,同时携带imports导入资源
.then(results => { // 返回的结果中有,有两个属性
// 一个是`module`, 这是已编译的 `WebAssembly.module`
// 一个是`instance`, `WebAssembly.Instance`, 也就是WebAssembly.module的第一个实例
const exports = results.instance.exports; // instance.exports 携带了 rust 中 pub extern 声明的函数(即导出的函数)
exports.hello_call_js(); // 调用
})
复制代码
现在我们的main.js
应该长这样:
const imports = {
env: {
hello_insert_dom: () => {
const h1 = document.createElement('h1');
h1.innerHTML = 'Hello, world';
const body = document.querySelector('body');
body.appendChild(h1);
}
}
};
fetch('build/hello.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports))
.then(results => {
console.log(results);
const exports = results.instance.exports;
exports.hello_call_js();
// exports.hello_call_js_pass_data();
});
复制代码
测试,激动人心!
首先开启一个本地服务, 我用的lighttpd, 这个随意
localhost!
接下来的计划
hello_world并不会就此结束,为了进一步了解WebAssembly 我准备通过各种方式实现hello_world来实践WebAssembly
e.g. 通过在WebAssembly中向JS函数传递参数来实现Hello,world