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
複製程式碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebAssembly</title>
</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