首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何导入WASM (Rust)中的WASM模块并传递字符串参数

如何导入WASM (Rust)中的WASM模块并传递字符串参数
EN

Stack Overflow用户
提问于 2022-06-14 14:14:26
回答 1查看 1.3K关注 0票数 9

我想在Wasm模块内部实例化一个Wasm模块,遵循这个js-sys示例。在本例中,调用add函数传递i32参数。

我创建了一个hello world函数,它接受一个字符串作为参数,并返回一个字符串。但是,调用此函数不起作用,因为它返回未定义的函数。

通常,wasm bindgen生成胶水代码,该代码创建上下文并将字符串放在堆栈上。但是,没有为Rust生成这样的代码。

如何从Rust中的Wasm加载和执行hello函数?

imported_lib.rs

代码语言:javascript
复制
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
 a + b
}

#[wasm_bindgen]
pub fn hello(name: String) -> String {
 format!("hello {:?}", name).into()
}

main_lib.rs

代码语言:javascript
复制
use js_sys::{Function, Object, Reflect, WebAssembly};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::{spawn_local, JsFuture};

// lifted from the `console_log` example
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(a: &str);
}

macro_rules! console_log {
    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}

const WASM: &[u8] = include_bytes!("imported_lib.wasm");

async fn run_async() -> Result<(), JsValue> {
 let a = JsFuture::from(WebAssembly::instantiate_buffer(WASM, &Object::new())).await?;
 let b: WebAssembly::Instance = Reflect::get(&a, &"instance".into())?.dyn_into()?;
 let c = b.exports();

 let add = Reflect::get(c.as_ref(), &"add".into())?
  .dyn_into::<Function>()
  .expect("add export wasn't a function");
 let three = add.call2(&JsValue::undefined(), &1.into(), &2.into())?;
 console_log!("1 + 2 = {:?}", three); // 1 + 2 = JsValue(3)

 let hello = Reflect::get(c.as_ref(), &"hello".into())?
  .dyn_into::<Function>()
  .expect("hello export wasn't a function");
 let hello_world = hello.call1(&JsValue::undefined(), &"world".into());
 console_log!("{:?}", hello_world); // JsValue(undefined)

 Ok(())
}

#[wasm_bindgen(start)]
pub fn run() {
 spawn_local(async {
  run_async().await.unwrap_throw();
 });
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-06-21 18:38:18

我花了好几天时间才解决这个问题。希望能帮上忙!因为这里有很多信息要打包。我会尽量简短,但如果你想知道更多,让我知道,我会详述我的答案。

对为什么会发生这种情况的简短解释

这实际上是因为wasm在默认情况下不返回Strings,所以wasm-bindgen的聪明人做了一些事情,所以当您运行wasm-pack build时,它会生成一个为您执行此操作的js代码。函数hello不返回string,而是返回指针。要证明这一点,可以检查生成imported_lib.rs时生成的文件。

您可以看到它生成的文件imported_lib.wasm.d.ts如下所示:

代码语言:javascript
复制
export const memory: WebAssembly.Memory;
export function add(a: number, b: number): number;
export function hello(a: number, b: number, c: number): void;
export function popo(a: number): void;
export function __wbindgen_add_to_stack_pointer(a: number): number;
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number): number;
export function __wbindgen_free(a: number, b: number): void;
  • 您可以看到,函数add与声明的方式相匹配,2个参数并返回一个number。另一方面,您可以看到函数hello接受3个参数并返回一个void (与声明的方式非常不同)。
  • 您还可以看到,命令wasp-pack build生成了一些额外的函数,如(__wbindgen_add_to_stack_pointer__wbindgen_free等)。有了这些函数,他们就能够得到字符串。

命令wasm-pack build生成的另一个文件是imported_lib_bg.js。在这个文件中,您可以看到它们导出函数hello。这里是JavaScript调用已编译的wasm函数并将指针“转换”到实际字符串的地方。

因此,基本上您必须做一些类似于文件imported_lib_bg.js中的事情。我就是这样做的:

解决方案

在主项目中创建一个文件夹调用js,在该文件夹中创建一个文件调用getString.js。您的项目文件系统应该如下所示:

代码语言:javascript
复制
mainProject
├── js
    ├── getString.js
├── src
    ├── main_lib.rs
    ├── ...
├── www
├── ...

文件应该有以下内容:

代码语言:javascript
复制
function getInt32Memory0(wasm_memory_buffer) {
    let cachedInt32Memory0 = new Int32Array(wasm_memory_buffer);
    return cachedInt32Memory0;
}

function getStringFromWasm(ptr, len, wasm_memory_buffer) {
    const mem = new Uint8Array(wasm_memory_buffer);
    const slice = mem.slice(ptr, ptr + len);
    const ret = new TextDecoder('utf-8').decode(slice);
    return ret;
}

let WASM_VECTOR_LEN = 0;

function getUint8Memory0(wasm_memory_buffer) {
    let cachedUint8Memory0 = new Uint8Array(wasm_memory_buffer);
    return cachedUint8Memory0;
}

const lTextEncoder = typeof TextEncoder === 'undefined' ? (0, module.require)('util').TextEncoder : TextEncoder;

let cachedTextEncoder = new lTextEncoder('utf-8');

const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
    ? function (arg, view) {
    return cachedTextEncoder.encodeInto(arg, view);
}
    : function (arg, view) {
    const buf = cachedTextEncoder.encode(arg);
    view.set(buf);
    return {
        read: arg.length,
        written: buf.length
    };
});

function passStringToWasm0(arg, malloc, realloc, wasm_memory_buffer) {

    if (realloc === undefined) {
        const buf = cachedTextEncoder.encode(arg);
        const ptr = malloc(buf.length);
        getUint8Memory0(wasm_memory_buffer).subarray(ptr, ptr + buf.length).set(buf);
        WASM_VECTOR_LEN = buf.length;
        return ptr;
    }

    let len = arg.length;
    let ptr = malloc(len);

    const mem = getUint8Memory0(wasm_memory_buffer);

    let offset = 0;

    for (; offset < len; offset++) {
        const code = arg.charCodeAt(offset);
        if (code > 0x7F) break;
        mem[ptr + offset] = code;
    }

    if (offset !== len) {
        if (offset !== 0) {
            arg = arg.slice(offset);
        }
        ptr = realloc(ptr, len, len = offset + arg.length * 3);
        const view = getUint8Memory0(wasm_memory_buffer).subarray(ptr + offset, ptr + len);
        const ret = encodeString(arg, view);

        offset += ret.written;
    }

    WASM_VECTOR_LEN = offset;
    return ptr;
}


/**
* @param {&JsValue} wasm: wasm object
* @param {string} fn_name: function's name to call in the wasm object
* @param {string} name: param to give to fn_name
* @returns {string}
*/
export function getString(wasm, fn_name, name) {
    try {
        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
        const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc, wasm.memory.buffer);
        const len0 = WASM_VECTOR_LEN;
        //wasm.hello(retptr, ptr0, len0);
        wasm[fn_name](retptr, ptr0, len0);
        var r0 = getInt32Memory0(wasm.memory.buffer)[retptr / 4 + 0];
        var r1 = getInt32Memory0(wasm.memory.buffer)[retptr / 4 + 1];
        return getStringFromWasm(r0, r1, wasm.memory.buffer);
    } finally {
        wasm.__wbindgen_add_to_stack_pointer(16);
        wasm.__wbindgen_free(r0, r1);
    }
}

在您的main_lib.rs中添加以下内容:

代码语言:javascript
复制
...

#[wasm_bindgen(module = "/js/getStrings.js")]
extern "C" {
    fn getString(wasm: &JsValue, nf_name: &str, name: &str) -> String;
}

...

    let hello_out= getString(c.as_ref(), &"hello", "Arnold");
    console_log!("# hello returns: {:?}", hello_out);
...

那应该完全有效!

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72618553

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档