You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 05:24:54