某些程度上,在〈建立記憶體〉中,就已經提示過如何實作字串了,當時的範例使用 (data (i32.const 0) "\48\65\6C\6C\6F")
,在記憶體中置入了五個位元組,若以無號 8 位元整數來看,正好是 "Hello"
五個字元的 Unicode 號碼。
在 JavaScript 環境中,從匯出的記憶體中取得 ArrayBuffer
,取得前五個位元組,使用 String.fromCharCode
轉成了 JavaScript 字串。
String.fromCharCode
是依 UniCode 號碼來判斷,而 "\48\65\6C\6C\6F"
的寫法,是用來指定每個位元組的值,英文字母 "Hello"
正好是一個位元組就可以表示各自的 Unicode 號碼,如果是中文呢?例如,'良'
這個字元?
良
的 Unicode 號碼是 33391,使用十六進位表示是 826F,不過別忘了,WebAssembly 採用 Little-endian,低位元組在前,高位元組在後,因此要寫成 "6F82"
。
例如,要在 JavaScript 中,可以使用 String.fromCharCode
產生 "良葛格"
字串,'葛'
的 Unicode 號碼是 33883,十六進位表示是 845B,'格'
的 Unicode 號碼是 26684,十六進位表示是 683C,這時就要寫為:
(module
(memory $mem 1)
(data (i32.const 0) "\6F\82\5B\84\3C\68")
(export "mem" (memory $mem))
(func $nope)
)
在 JavaScript 中,由於這三個字的 Unicode 號碼,都必須使用兩個位元組表示,因此要使用 Uint16Array
:
WebAssembly.instantiateStreaming(fetch('program.wasm'))
.then(prog => {
console.log(String.fromCharCode.apply(null,
new Uint16Array(prog.instance.exports.mem.buffer, 0, 48)
));
});
你可以在使用 data
時,直接以字元來表示記憶體中該有的資料,例如:
(data (i32.const 0) "Hello")
這時要注意,Wat 檔案要使用 UTF-8 編碼,因此,編譯時讀取 Wat 檔案時,讀到 "Hello"
的部份,位元組會是 "\48\65\6C\6C\6F"
,就每個位元組來看,剛好就是 "Hello"
中五個字元的 Unicode 號碼,這是因為 UTF-8 在英數字元部份,與原本 Ascii 編碼一致的關係。
如果你寫中文的話會如何呢?
(data (i32.const 0) "良葛格")
因為使用 UTF-8 儲存 Wat 檔案,"良葛格"
這部份被儲存為 "\e8\89\af\e8\91\9b\e6\a0\bc"
,也就是相當於寫為:
(data (i32.const 0) "\e8\89\af\e8\91\9b\e6\a0\bc"))
UTF-8 基本上使用三個位元組來表示編碼中文字元,例如,"\e8\89\af"
是 '良'
這個字儲存時,採用的 UTF-8 編碼,不要將編碼與 Unicode 號碼搞錯了,同一個 Unicode 字元,可以有 UTF-8、UTF-16、UTF-32 等編碼方式,例如,良
的 Unicode 號碼是 33391,而它的 UTF-8 編碼是 "\e8\89\af"
。
也就是這相當於:
(module
(memory $mem 1)
(export "mem" (memory $mem))
(func $main
;; 良
(i32.store8 (i32.const 0) (i32.const 0xE8))
(i32.store8 (i32.const 1) (i32.const 0x89))
(i32.store8 (i32.const 2) (i32.const 0xAF))
;; 葛
(i32.store8 (i32.const 3) (i32.const 0xE8))
(i32.store8 (i32.const 4) (i32.const 0x91))
(i32.store8 (i32.const 5) (i32.const 0x9B))
;; 格
(i32.store8 (i32.const 6) (i32.const 0xE6))
(i32.store8 (i32.const 7) (i32.const 0xA0))
(i32.store8 (i32.const 8) (i32.const 0xBC))
)
(start $main)
)
結論就是,這時不能直接用 String.fromCharCode
來解釋記憶體中的資料,有個方便的 TextDecoder
API 可以解決這件事:
WebAssembly.instantiateStreaming(fetch('program.wasm'))
.then(prog => {
var bytes = new Uint8Array(prog.instance.exports.mem.buffer, 0, 9);
var string = new TextDecoder('utf8').decode(bytes);
console.log(string);
});