title: [WebAssembly 入門] 第二次的 Hello, world!
date: 2018-3-29 14:45:00
categories: WebAssembly, 筆記
tags: WebAssembly, JavaScript, Rust, LLVM toolchain
Reference link: 參考連結
auther: Yiniau
[WebAssembly 入門] 第二次的 Hello, world!
上一次Hello,world做了最基礎的實現,主要的程式碼如下
src/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();
}
}
複製程式碼
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();
});
複製程式碼
ok, 現在讓我們來換一個方式實現這個hello,world ———— 通過在WebAssembly中向JS函式傳遞引數來實現
傳遞引數
但是傳參在 MVP 版本的WebAssebly中並不是一件容易的事, WebAssembly只支援整數或浮點數 (支援 i32/i64/f32/f64值型別以及 i8/i16 儲存。), 並沒有對String的原生支援
想要與例項互動, 可以通過以下屬性
exports
- JS呼叫WebAssembly函式的地方, WebAssembly返回的值imports
- 當WebAssembly呼叫JS時, 具有任意數量的值型別(請注意: 這即不是陣列也不是可變引數, 長度必須在Module編譯時已知)Memory.buffer
- 這是一個ArrayBuffer,可以使用Uint8Array(等等)進行序列化
所有的方法中似乎直接訪問緩衝區是最簡單的: 首先我們要在JS中建立一個記憶體物件並序列化它:
// create a memory, Size is in pages.
const memory = new WebAssembly.Memory({ initial: 2 }); // 建立了2頁的記憶體,每個page 65536B -> 64KiB
const buffer = new Uint8Array(memory.buffer); // index arrayBuffer
// 當字元全部是英文時u8足夠使用
// 即只有0~255
複製程式碼
然後在env屬性中建立存值函式:
env: {
...
save_char_to_memory(num, index) {
buffer[index] = num;
},
...
}
複製程式碼
以及hello函式:
env: {
save_char_to_memory(num, index) {
buffer[index] = num;
},
log_string(size, index) {
let s = "";
for (let i = index; i < index + size; ++i) {
s += String.fromCodePoint(buffer[i]);
}
console.log(s);
}
}
複製程式碼
接下來在rust中完成函式
extern {
fn save_char_to_memory(num: u8, index: usize);
fn log_string(size: usize, index: usize);
}
/// return "Hello, world"'s bytes array
#[no_mangle]
pub extern fn pass_str() { // equal pub extern "C" fn ...
let s = b"Hello, world";
unsafe {
for i in 0..s.len() {
save_char_to_memory(s[i], i);
}
log_string(s.len(), 0);
}
}
複製程式碼
測試!
測試之前我們的程式碼應該長這樣:
src/lib.rs
:
//! a WebAssembly module with Rust
extern {
fn save_char_to_memory(num: u8, index: usize);
fn log_string(size: usize, index: usize);
}
/// return "Hello, world"'s bytes array
#[no_mangle]
pub extern fn pass_str() { // equal pub extern "C" fn ...
let s = b"Hello, world";
unsafe {
for i in 0..s.len() {
save_char_to_memory(s[i], i);
}
log_string(s.len(), 0);
}
}
複製程式碼
main.js
:
// create a memory, Size is in pages.
const memory = new WebAssembly.Memory({ initial: 2 });
const buffer = new Uint8Array(memory.buffer); // index arrayBuffer
const imports = {
memory: memory,
env: {
save_char_to_memory(num, index) {
buffer[index] = num;
},
log_string(size, index) {
let s = "";
for (let i = index; i < index + size; i++) {
s += String.fromCodePoint(buffer[i]);
}
console.log(s);
}
}
};
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.pass_str();
});
複製程式碼
編譯執行吧!
但是,沒有更優雅的方式嗎?
結果顯而易見,之前的文章中有講到WebAssembly也能夠使用memory,但是無法接觸到memory範圍之外的資料。
那麼讓我們先列印一下WebAssembly例項的內容
main.js:
fetch('build/hello.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiateStreaming(bytes, imports))
.then(results => {
console.log(results);
});
複製程式碼
src/lib.rs:
//! a WebAssembly module with Rust
extern {
fn log_string(ptr: *const u8, len: u16);
}
/// pass str | slice to js side
#[no_mangle]
pub extern fn pass_str() { // equal pub extern "C" fn ...
...
}
複製程式碼
result:
嗯??,memory?
看來這個就是WebAssembly自動建立的Memory例項,並且在附加在exports.instance
中傳遞到了JS端
那麼我們就能夠自然地假設js直接從WebAssembly中獲取字串的裸指標和長度就能夠從memory中擷取一段buffer並使用Uint8Array
序列化,再使用String.fromCharCode
翻譯為js String
try:
main.js:
...
const imports = {
memory: memory,
env: {
store_string_to_buffer(ptr, len) {
// buffer slice
let buf = new Uint16Array(mem.buffer, ptr, len);
let msg = new TextDecoder('utf8').decode(buf);
// 這是個新的api,你也可以用下面的方法
// let msg = String.fromCharCode(...buf);
console.log('msg: ', msg);
console.log('mbuf: ', buf);
}
}
};
...
複製程式碼
src/lib.rs:
//! a WebAssembly module with Rust
extern {
fn log_string(ptr: *const u8, len: u16);
}
/// return "Hello, world"'s bytes array
#[no_mangle]
pub extern fn pass_str() { // equal pub extern "C" fn ...
let s = "Hello, world";
unsafe {
log_string(s.as_ptr(), s.len() as u16);
}
}
複製程式碼
Second attempt:
yeap! 一切如自己所料的感覺真不錯!?
但是!
memory物件應該是共享的才對!這樣JS端也能夠向WebAssembly傳值...
算了,還是留待下回分解吧 ?