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

发布于 2025-02-07 03:51:06 字数 2162 浏览 1 评论 0原文

我想从WASM内部实例化WASM模块,之后此JS-Sys示例。在示例中,调用add函数,该功能传递了i32参数。

我创建了一个Hello World函数,该功能将字符串作为参数并返回字符串。但是,将此功能调用不起作用,因为它返回未定义。

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

如何从Rust中的WASM加载并执行Hello函数?

imported_lib.rs
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
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();
 });
}

I want to instantiate a Wasm module from inside a Wasm module, following this js-sys example. In the example, the add function is called which passes i32 parameters.

I've created a hello world function, which takes a string as a parameter and returns a string. However, calling this function doesn't work, as it returns undefined.

Normally wasm bindgen generates glue code which creates a context and puts the string on the stack. However, no such code is generated for Rust.

How can I load and execute the hello function from Wasm in Rust?

imported_lib.rs
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
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();
 });
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

筱果果 2025-02-14 03:51:06

我确实花了几天的时间来解决这个问题。我希望它有帮助!
由于在这里包装很多信息。我将尝试简短,但是如果您想了解更多,请让我知道,我会扩展我的答案。

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

发生这种情况,因为默认情况下,WASM不返回strings,因此wasm-bindgen的智能人员在运行时做了一些事情-pack Build它生成了为您执行此操作的JS代码。功能Hello不会返回字符串,而是返回指针。要证明这一点,您可以检查构建imported_lib.rs时生成的文件,

您可以看到它生成了文件imported_lib.wasm.d.ts此:

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,在该文件夹中创建文件调用gets> gets tring.js。您的项目文件系统应该看起来像这样:

mainProject
├── js
    ├── getString.js
├── src
    ├── main_lib.rs
    ├── ...
├── www
├── ...

并且文件应该具有以下方式:

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中,请添加以下内容:


...

#[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);
...

应该完全工作!

It really took me days to solve this problem. I hope it helps!
Since it's a lot of info to pack here. I will try to keep it short but If you want to know more let me know and I'll expand on my answer.

Short explanation of why this is happening

This actually happens because wasm by default does not return Strings so the smart people at wasm-bindgen did something so when you run wasm-pack build it generates a js code that do this for you. The function hello does not return a string, instead returns a pointer. To proof this, you can check the files generated when you build the imported_lib.rs

You can see that it generates the file imported_lib.wasm.d.ts that looks something like this:

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;
  • You can see that the function add does match how you declared, 2 parameters and returns a number. In the other hand you can see that the function hello takes 3 parameters and return a void (very different to how you declared)
  • You can also see that the command wasp-pack build generated some extra functions like (__wbindgen_add_to_stack_pointer, __wbindgen_free, etc). With these functions they are able to get the string.

The other file that the command wasm-pack build generates is imported_lib_bg.js. In this file you can see that they export the function hello. Here it's where JavaScript call the compiled wasm function and "translate" the pointer to the actual string.

So basically you would have to do something similar to what it is in the file imported_lib_bg.js. This is how I did it:

Solution

In your main project create a folder call js, and inside that folder create a file call getString.js. Your project filesystem should look something like this:

mainProject
├── js
    ├── getString.js
├── src
    ├── main_lib.rs
    ├── ...
├── www
├── ...

And the file should have this:

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);
    }
}

In your main_lib.rs add this:


...

#[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);
...

That should totally work!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文