如何从Rust向外部WASM模块传递指针并允许其修改对应值?
问题与解决方案
问题
在HTML页面中让用户选择外部.wasm文件,将其函数传递给已加载的Rust WASM模块,尝试通过传递指针让外部函数修改主模块内的变量,但变量值始终未改变(预期将val从7改为5)。
代码示例
外部WASM模块(Rust)
#[wasm_bindgen] pub fn modify(val: *mut u8) { let val = unsafe { &mut *val }; *val = 5; }
主调用WASM模块(Rust)
#[wasm_bindgen] pub fn call_modify(modify: Function) { let mut val = 7u8; let val_ptr = &mut val as *mut u8; console_log!("before {val}"); modify.call1(&JsValue::NULL, &JsValue::from(val_ptr)).unwrap(); console_log!("after {val}"); }
HTML页面代码
<button id="load-wasm-file-btn">Load Wasm File</button> <input type="file" id="wasm-file-input" accept=".js" style="display: none;" /> <button id="load-wasm-data-btn">Load Wasm Data</button> <input type="file" id="wasm-data-input" accept=".wasm" style="display: none;" /> <script type="module"> import init, {call_modify} from "./pkg/dynamic_wasm_loading.js"; await init(); var wasm_url; document.getElementById("load-wasm-data-btn").addEventListener("click", () => { document.getElementById("wasm-data-input").click(); }); document.getElementById("wasm-data-input").addEventListener("change", async (e) => { const file = e.target.files[0]; wasm_url = URL.createObjectURL(file); }); document.getElementById("load-wasm-file-btn").addEventListener("click", () => { document.getElementById("wasm-file-input").click(); }); document.getElementById("wasm-file-input").addEventListener("change", async (e) => { const file = e.target.files[0]; const fileURL = URL.createObjectURL(file); import(fileURL).then(async (module) => { console.log(wasm_url); console.log("initializing..."); console.log(module); await module.default(wasm_url); console.log("initialized"); console.log(module.modify); call_modify(module.modify); }).catch((error) => { console.error('Error importing module:', error); }); }); </script>
原因分析
每个WASM模块在浏览器环境中拥有独立的线性内存空间,你传递的指针是主模块内存中的偏移地址,外部WASM模块无法访问主模块的内存,因此修改操作只会作用于自身内存的无效区域,不会影响主模块的变量。
解决方案
方案1:JavaScript中转数据
放弃指针传递,通过函数返回值完成数据更新:
外部模块修改
#[wasm_bindgen] pub fn modify(_val: u8) -> u8 { 5 // 返回修改后的值 }
主模块修改
#[wasm_bindgen] pub fn call_modify(modify: Function) { let mut val = 7u8; console_log!("before {val}"); // 调用外部函数并接收返回值 let result = modify.call1(&JsValue::NULL, &JsValue::from(val)).unwrap(); val = result.as_f64().unwrap() as u8; console_log!("after {val}"); }
方案2:共享内存(WebAssembly.Memory)
让两个WASM模块使用同一块共享内存,实现指针跨模块有效:
主模块创建并导出共享内存
use wasm_bindgen::prelude::*; use web_sys::WebAssembly::Memory; #[wasm_bindgen] pub struct SharedMemory { mem: Memory, } #[wasm_bindgen] impl SharedMemory { #[wasm_bindgen(constructor)] pub fn new() -> Result<Self, JsValue> { // 创建初始10页、最大10页的共享内存(每页64KB) let mem = Memory::new(&js_sys::Object::from_entries(&js_sys::Array::from_iter(&[ js_sys::Array::from_iter(&[JsValue::from("initial"), JsValue::from(10)]), js_sys::Array::from_iter(&[JsValue::from("maximum"), JsValue::from(10)]), ])))?; Ok(Self { mem }) } #[wasm_bindgen(getter)] pub fn memory(&self) -> Memory { self.mem.clone() } } #[wasm_bindgen] pub fn call_modify(modify: Function, mem: Memory) { unsafe { let mem_ptr = mem.buffer().as_mut_ptr() as *mut u8; // 在共享内存偏移0处存放变量 let val_ptr = mem_ptr.add(0); *val_ptr = 7u8; console_log!("before {}", *val_ptr); // 传递共享内存中的偏移量(而非原始指针) modify.call1(&JsValue::NULL, &JsValue::from(0u32)).unwrap(); console_log!("after {}", *val_ptr); } }
外部模块导入共享内存
#[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = "memory")] static MEMORY: js_sys::WebAssembly::Memory; } #[wasm_bindgen] pub fn modify(offset: u32) { unsafe { let mem_ptr = MEMORY.buffer().as_mut_ptr() as *mut u8; let val = mem_ptr.add(offset as usize); *val = 5; } }
HTML代码修改(传递共享内存)
import init, {call_modify, SharedMemory} from "./pkg/dynamic_wasm_loading.js"; await init(); // 创建共享内存实例 const sharedMem = new SharedMemory(); // ... 其余代码不变 import(fileURL).then(async (module) => { // 初始化外部模块时传入共享内存 await module.default(wasm_url, { memory: sharedMem.memory() }); console.log("initialized"); // 将共享内存传递给主模块函数 call_modify(module.modify, sharedMem.memory()); }).catch((error) => { console.error('Error importing module:', error); });
方案3:主模块导出修改函数
主模块提供修改内部变量的导出函数,外部模块调用该函数完成更新:
主模块修改
use wasm_bindgen::prelude::*; static mut VAL: u8 = 7; #[wasm_bindgen] pub fn get_val() -> u8 { unsafe { VAL } } #[wasm_bindgen] pub fn set_val(new_val: u8) { unsafe { VAL = new_val }; } #[wasm_bindgen] pub fn call_modify(modify: Function) { console_log!("before {}", unsafe { VAL }); // 将set_val函数传递给外部模块 modify.call1(&JsValue::NULL, &JsValue::from(set_val)).unwrap(); console_log!("after {}", unsafe { VAL }); }
外部模块修改
#[wasm_bindgen] pub fn modify(set_val: js_sys::Function) { // 调用主模块的set_val函数修改值 set_val.call1(&JsValue::NULL, &JsValue::from(5)).unwrap(); }
内容的提问来源于stack exchange,提问作者Patrik Číhal
相关产品推荐
相关产品推荐

