下面這段代碼忽略了錯誤處理機制,介紹瞭如何在 Go 語言開發的宿主程序中嵌入 WebAssembly.
func createWasmVM(code []byte) {
engine := wasmtime.NewEngine()
module, _ := wasmtime.NewModule(engine, code)
store := wasmtime.NewStore(engine)
linker := wasmtime.NewLinker(engine)
inst, _ := linker.Instantiate(store, module)
_ = inst
}
這段代碼涉及到幾個重要的 WebAssembly 的概念,簡單介紹如下:
- 引擎(Engine):用於編譯和管理 wasm 模塊的全局上下文。
- 模塊(Module):已編譯的 WebAssembly 模塊。 該結構表示實例化後準備執行的內存中 JIT 代碼。
- 存儲(Store):所有 WebAssembly 對象和主機值都將“連接”到存儲。
- 實例(Instance):一個實例化的 WebAssembly 模塊,您可以從中實際獲取一個函數,例如調用。實例化時,調用模塊的啓動函數。
- 鏈接器(僅限 Wasmtime):將 wasm 模塊/實例鏈接在一起的輔助結構。
上面的代碼雖然創建了一個 WebAssembly 模塊的實例,但是根據 WebAssembly 規範,start 函數會被執行。 但由於安全限制,無法輸出執行結果,所以即使執行了也沒效果。 因此,我們需要實現宿主程序與 WebAssembly 程序的互操作,為WebAssembly 程序提供輸入/輸出接口。
假設我們的 WebAssembly 程序有一個名為 sum 的函數,它接收兩個整數變量作為參數並返回它們的和,宿主程序可以使用下面的代碼來調用這個函數:
fn := inst.GetExport(store, "sum").Func()
r, _ := fn.Call(store, 1, 2)
fmt.Println(r.(int32))
雖然具體的調用方式與宿主程序的編程語言和所使用的WebAssembly運行時不同,但是運行時的文檔一般都有相關的説明,按照文檔來做就好了。
這裏的難點在於如何從 WebAssembly 程序中導出 sum 函數,以便宿主程序可以找到並調用它。 前面説了,只要有編譯器,任何語言都可以編譯成WebAssembly,但是大部分語言在設計時都沒有考慮WebAssembly的需求,也沒有辦法在WebAssembly中導出函數。所以這個問題只能通過特定編譯器的非標準擴展來解決。也就是説,找到這個非標準擴展是解決問題最關鍵的一步。