嵌入 Wasm 函数

WasmEdge Go SDK 允许将 WebAssembly 函数嵌入到一个 Go 主程序。可以使用 Go SDK API 来传递调用参数到嵌入的 WebAssembly 函数,然后捕获返回值。

然而,WebAssembly 规范本身只支持一些简单数据类型。 它不支持 字符串和数组等类型。 为了将 Go 中的丰富类型传递给 WebAssembly,我们可以手动编码内存指针(点击这里),或者使用管理数据交换的自动化工具。

wasmedge-bindgen 项目为函数提供了 Rust 宏来接受和返回复杂的数据类型,然后让 Go 函数调用在 WasmEdge 中运行的此类 Rust 函数。 本章中演示的完整源代码 可在此处获得

将 Rust 函数编译成 WebAssembly

Rust 项目 中,你只需要用 “[wasmedge_bindgen] ”宏来注释 [你的函数](https ://github.com/second-state/WasmEdge-go-examples/blob/master/wasmedge-bindgen/go_BindgenFuncs/rust_bindgen_funcs/src/lib.rs) 。 这些带注释的函数将由 Rust 编译器自动检测并转换为可以从 wasmedge-bindgen GO SDK 调用的 WebAssembly 函数。 在下面的示例中,我们有几个 Rust 函数可以进行复杂的调用参数并返回复杂值。

  1. #![allow(unused)]
  2. fn main() {
  3. use wasmedge_bindgen::*;
  4. use wasmedge_bindgen_macro::*;
  5. use num_integer::lcm;
  6. use sha3::{Digest, Sha3_256, Keccak256};
  7. use serde::{Serialize, Deserialize};
  8. #[derive(Serialize, Deserialize, Debug)]
  9. struct Point {
  10. x: f32,
  11. y: f32
  12. }
  13. #[derive(Serialize, Deserialize, Debug)]
  14. struct Line {
  15. points: Vec<Point>,
  16. valid: bool,
  17. length: f32,
  18. desc: String
  19. }
  20. #[wasmedge_bindgen]
  21. pub fn create_line(p1: String, p2: String, desc: String) -> Result<Vec<u8>, String> {
  22. let point1: Point = serde_json::from_str(p1.as_str()).unwrap();
  23. let point2: Point = serde_json::from_str(p2.as_str()).unwrap();
  24. let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();
  25. let valid = if length == 0.0 { false } else { true };
  26. let line = Line { points: vec![point1, point2], valid: valid, length: length, desc: desc };
  27. return Ok(serde_json::to_vec(&line).unwrap());
  28. }
  29. #[wasmedge_bindgen]
  30. pub fn say(s: String) -> Result<Vec<u8>, String> {
  31. let r = String::from("hello ");
  32. return Ok((r + s.as_str()).as_bytes().to_vec());
  33. }
  34. #[wasmedge_bindgen]
  35. pub fn obfusticate(s: String) -> Result<Vec<u8>, String> {
  36. let r: String = (&s).chars().map(|c| {
  37. match c {
  38. 'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,
  39. 'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,
  40. _ => c
  41. }
  42. }).collect();
  43. Ok(r.as_bytes().to_vec())
  44. }
  45. #[wasmedge_bindgen]
  46. pub fn lowest_common_multiple(a: i32, b: i32) -> Result<Vec<u8>, String> {
  47. let r = lcm(a, b);
  48. return Ok(r.to_string().as_bytes().to_vec());
  49. }
  50. #[wasmedge_bindgen]
  51. pub fn sha3_digest(v: Vec<u8>) -> Result<Vec<u8>, String> {
  52. return Ok(Sha3_256::digest(&v).as_slice().to_vec());
  53. }
  54. #[wasmedge_bindgen]
  55. pub fn keccak_digest(s: Vec<u8>) -> Result<Vec<u8>, String> {
  56. return Ok(Keccak256::digest(&s).as_slice().to_vec());
  57. }
  58. }

你可以使用标准的 Cargo 命令构建 WebAssembly 字节码文件。

$ cd rust_bindgen_funcs $ cargo build --target wasm32-wasi --release # 输出 WASM 将是 target/wasm32-wasi/release/rust_bindgen_funcs_lib.wasm. $ cp target/wasm32-wasi/release/rust_bindgen_funcs_lib.wasm ../ $ cd ../

Go host 程序

Go host 程序中,你可以使用 WasmEdge Go SDK 创建和设置 WasmEdge VM。 但是,你现不应该调用 vm.Instantiate() 而应该调用bindgen.Instantiate(vm) 来实例化 VM 并返回 bindgen 对象。

func main() { // 预期的 Args[0]: 程序名 (./bindgen_funcs) // 预期的 Args[1]: wasm 文件 (rust_bindgen_funcs_lib.wasm)) wasmedge.SetLogErrorLevel() var conf = wasmedge.NewConfigure(wasmedge.WASI) var vm = wasmedge.NewVMWithConfig(conf) var wasi = vm.GetImportModule(wasmedge.WASI) wasi.InitWasi( os.Args[1:], // The args os.Environ(), // The envs []string{".:."}, // The mapping preopens ) vm.LoadWasmFile(os.Args[1]) vm.Validate() // 实例化 bindgen 和 vm bg := bindgen.Instantiate(vm)

接下来,你可以通过 bindgen 对象调用 VM 中的任何 [wasmedge_bindgen] 注释函数。

// create_line: string, string, string -> string (输入已 JSON 字符串化) res, err := bg.Execute("create_line", "{\"x\":2.5,\"y\":7.8}", "{\"x\":2.5,\"y\":5.8}", "A thin red line") if err == nil { fmt.Println("Run bindgen -- create_line:", string(res)) } else { fmt.Println("Run bindgen -- create_line FAILED", err) } // say: string -> string res, err = bg.Execute("say", "bindgen funcs test") if err == nil { fmt.Println("Run bindgen -- say:", string(res)) } else { fmt.Println("Run bindgen -- say FAILED") } // obfusticate: string -> string res, err = bg.Execute("obfusticate", "A quick brown fox jumps over the lazy dog") if err == nil { fmt.Println("Run bindgen -- obfusticate:", string(res)) } else { fmt.Println("Run bindgen -- obfusticate FAILED") } // lowest_common_multiple: i32, i32 -> i32 res, err = bg.Execute("lowest_common_multiple", int32(123), int32(2)) if err == nil { num, _ := strconv.ParseInt(string(res), 10, 32) fmt.Println("Run bindgen -- lowest_common_multiple:", num) } else { fmt.Println("Run bindgen -- lowest_common_multiple FAILED") } // sha3_digest: array -> array res, err = bg.Execute("sha3_digest", []byte("This is an important message")) if err == nil { fmt.Println("Run bindgen -- sha3_digest:", res) } else { fmt.Println("Run bindgen -- sha3_digest FAILED") } // keccak_digest: array -> array res, err = bg.Execute("keccak_digest", []byte("This is an important message")) if err == nil { fmt.Println("Run bindgen -- keccak_digest:", res) } else { fmt.Println("Run bindgen -- keccak_digest FAILED") } bg.Release() vm.Release() conf.Release() }

最后,你可以构建并运行 Go host 程序。

go build ./bindgen_funcs rust_bindgen_funcs_lib.wasm

本示例的标准输出如下。

Run bindgen -- create_line: {"points":[{"x":1.5,"y":3.8},{"x":2.5,"y":5.8}],"valid":true,"length":2.2360682,"desc":"A thin red line"} Run bindgen -- say: hello bindgen funcs test Run bindgen -- obfusticate: N dhvpx oebja sbk whzcf bire gur ynml qbt Run bindgen -- lowest_common_multiple: 246 Run bindgen -- sha3_digest: [87 27 231 209 189 105 251 49 159 10 211 250 15 159 154 181 43 218 26 141 56 199 25 45 60 10 20 163 54 211 195 203] Run bindgen -- keccak_digest: [126 194 241 200 151 116 227 33 216 99 159 22 107 3 177 169 216 191 114 156 174 193 32 159 246 228 245 133 52 75 55 27]