[WebAssembly 入門] Hello, world!

Yiniau發表於2018-03-29

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!

[WebAssembly 入門] Hello, world!

接下來的計劃

hello_world並不會就此結束,為了進一步瞭解WebAssembly 我準備通過各種方式實現hello_world來實踐WebAssembly

e.g. 通過在WebAssembly中向JS函式傳遞引數來實現Hello,world

相關文章