将JavaScript字符串传递给编译为WebAssembly的Rust函数

vin*_*def 7 javascript string rust webassembly

我有这个简单的Rust函数:

#[no_mangle]
pub fn compute(operator: &str, n1: i32, n2: i32) -> i32 {
    match operator {
        "SUM" => n1 + n2,
        "DIFF" => n1 - n2,
        "MULT" => n1 * n2,
        "DIV" => n1 / n2,
        _ => 0
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在成功地将其编译为WebAssembly,但是没有设法将operator参数从JS 传递给Rust.

调用Rust函数的JS行如下所示:

instance.exports.compute(operator, n1, n2);
Run Code Online (Sandbox Code Playgroud)

operator是JS Stringn1,n2是JS Number秒.

n1并且n2正确传递并且可以在编译函数内部读取,所以我猜问题是我如何传递字符串.我想它是作为从JS到WebAssembly的指针传递,但无法找到有关其工作原理的证据或材料.

我没有使用Emscripten并希望将其保持独立(编译目标wasm32-unknown-unknown),但我看到它们将其编译的函数包装起来Module.cwrap,也许这可能会有所帮助?

She*_*ter 14

要在JavaScript和Rust之间传输字符串数据,您需要决定

  1. 文本的编码:UTF-8(Rust native)或UTF-16(JS native).
  2. 谁将拥有内存缓冲区:JS(调用者)或Rust(被调用者).
  3. 如何表示字符串数据和长度:NUL终止(C风格)或不同长度(Rust风格).
  4. 如果它们是分开的,如何传达数据和长度.

解决方案1

我决定:

  1. 要将JS字符串转换为UTF-8,这意味着TextEncoderJS API最适合.
  2. 调用者应该拥有内存缓冲区.
  3. 使长度为单独的值.
  4. 应该进行另一个结构和分配来保存指针和长度.

LIB/src.rs

[package]
name = "quick-maths"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]

[lib]
crate-type = ["cdylib"]
Run Code Online (Sandbox Code Playgroud)

为WASM构建C dylib以帮助它们缩小尺寸非常重要.

Cargo.toml

[target.wasm32-unknown-unknown]
rustflags = [
    "-C", "link-args=--import-memory",
]
Run Code Online (Sandbox Code Playgroud)

对于它的价值,我在Node中运行此代码,而不是在浏览器中运行.

index.js

{
  "name": "quick-maths",
  "version": "0.1.0",
  "main": "index.js",
  "author": "An Devloper <an.devloper@example.com>",
  "license": "MIT",
  "scripts": {
    "example": "node ./index.js"
  },
  "dependencies": {
    "fs-extra": "^8.0.1",
    "text-encoding": "^0.7.0"
  }
}
Run Code Online (Sandbox Code Playgroud)

解决方案2

我决定:

  1. 要将JS字符串转换为UTF-8,这意味着TextEncoderJS API最适合.
  2. 模块应该拥有内存缓冲区.
  3. 使长度为单独的值.
  4. 使用a Box<String>作为底层数据结构.这允许Rust代码进一步使用分配.

SRC/lib.rs

$ rustup component add rust-std --target wasm32-unknown-unknown
$ cargo build --release --target wasm32-unknown-unknown
Run Code Online (Sandbox Code Playgroud)

index.js

// A struct with a known memory layout that we can pass string information in
#[repr(C)]
pub struct JsInteropString {
    data: *const u8,
    len: usize,
}

// Our FFI shim function    
#[no_mangle]
pub unsafe extern "C" fn compute(s: *const JsInteropString, n1: i32, n2: i32) -> i32 {
    // Check for NULL (see corresponding comment in JS)
    let s = match s.as_ref() {
        Some(s) => s,
        None => return -1,
    };

    // Convert the pointer and length to a `&[u8]`.
    let data = std::slice::from_raw_parts(s.data, s.len);

    // Convert the `&[u8]` to a `&str`    
    match std::str::from_utf8(data) {
        Ok(s) => real_code::compute(s, n1, n2),
        Err(_) => -2,
    }
}

// I advocate that you keep your interesting code in a different
// crate for easy development and testing. Have a separate crate
// with the FFI shims.
mod real_code {
    pub fn compute(operator: &str, n1: i32, n2: i32) -> i32 {
        match operator {
            "SUM"  => n1 + n2,
            "DIFF" => n1 - n2,
            "MULT" => n1 * n2,
            "DIV"  => n1 / n2,
            _ => 0,
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,此过程可用于其他类型.你"只是"必须决定如何将数据表示为双方同意然后发送它的一组字节.

也可以看看:

  • 关于解决方案 1,如何确定您没有覆盖 rust 程序正在使用的内存?Rust 程序是否在单独的内存区域上分配? (2认同)