我想在Wasm模块内部实例化一个Wasm模块,遵循这个js-sys示例。在本例中,调用add函数传递i32参数。
我创建了一个hello world函数,它接受一个字符串作为参数,并返回一个字符串。但是,调用此函数不起作用,因为它返回未定义的函数。
通常,wasm bindgen生成胶水代码,该代码创建上下文并将字符串放在堆栈上。但是,没有为Rust生成这样的代码。
如何从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();
});
}发布于 2022-06-21 18:38:18
我花了好几天时间才解决这个问题。希望能帮上忙!因为这里有很多信息要打包。我会尽量简短,但如果你想知道更多,让我知道,我会详述我的答案。
对为什么会发生这种情况的简短解释
这实际上是因为wasm在默认情况下不返回Strings,所以wasm-bindgen的聪明人做了一些事情,所以当您运行wasm-pack build时,它会生成一个为您执行此操作的js代码。函数hello不返回string,而是返回指针。要证明这一点,可以检查生成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,在该文件夹中创建一个文件调用getString.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);
...那应该完全有效!
https://stackoverflow.com/questions/72618553
复制相似问题