使用 wasm-bindgen 对大型 rust 对象进行 Js 绑定

Posted

技术标签:

【中文标题】使用 wasm-bindgen 对大型 rust 对象进行 Js 绑定【英文标题】:Js binding for large rust object using wasm-bindgen 【发布时间】:2021-03-22 08:05:21 【问题描述】:

我想写一个显示大二进制文件内容的vscode扩展,用bincode写的:

#[macro_use]
extern crate serde_derive;

use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader, BufWriter;

#[derive(Serialize, Deserialize)]
pub struct MyValue 
    pub name: String,


#[derive(Serialize, Deserialize)]
pub struct MyStruct 
    pub data: HashMap<String, MyValue>,


impl MyStruct 
    pub fn dump(&self, filename: &str) -> Result<(), String> 
        let file = File::create(filename).map_err(|msg| msg.to_string())?;
        let writer = BufWriter::new(file);
        bincode::serialize_into(writer, self).map_err(|msg| msg.to_string())
    

    pub fn load(filename: &str) -> Result<Self, String> 
        let file = File::open(filename).map_err(|msg| msg.to_string())?;
        let reader = BufReader::new(file);
        bincode::deserialize_from::<BufReader<_>, Self>(reader).map_err(|msg| msg.to_string())
    

因此有一个wasm绑定:


#[wasm_bindgen]
#[derive(Clone)]
pub struct PyMyStruct 
    inner: Arc<MyStruct>,


#[wasm_bindgen]
impl PyMyStruct 
    pub fn new(filename: &str) -> Self 
        Self 
            inner: Arc::new(MyStruct::load(filename).unwrap()),
        
    

    pub fn header(self) -> Array 
        let keys = Array::new();
        for key in self.inner.data.keys() 
            keys.push(&JsValue::from_str(key));
        
        keys
    

    pub fn value(&self, name: &str) -> JsValue 
        if let Some(value) = self.inner.data.get(name) 
            JsValue::from_serde(value).unwrap_or(JsValue::NULL)
         else 
            JsValue::NULL
        
    

它为 javascript 世界提供了一个简单的接口,以便访问该文件的内容。 使用 Arc 以防止在 JavaScript 端处理时进行昂贵的意外内存复制。 (keys 没有被标记为可变,但 rust 编译器推荐这种方式可能看起来很奇怪)

运行测试代码时:

const PyMyStruct = require("./corejs.js");

let obj = new PyMyStruct("../../dump.spb")
console.log(obj.header())

您收到错误消息:

错误:空指针传递给 rust

有人知道如何处理这个用例吗?

谢谢!

【问题讨论】:

【参考方案1】:

这里的问题是您使用的是new PyMyStruct() 而不是PyMyStruct.new()。在 wasm-bindgen 的调试模式下,您将在运行时收到关于此的错误。使用.new() 将解决您的问题:

let obj = PyMyStruct.new("../../dump.spb")

如果您将#[wasm_bindgen(constructor)] 注释添加到new 方法,那么new PyMyStruct() 也将起作用:

#[wasm_bindgen]
impl PyMyStruct 
    #[wasm_bindgen(constructor)]
    pub fn new(filename: &str) -> Self 
        Self 
            inner: 1,
        
    

现在这很好:

let obj = new PyMyStruct("../../dump.spb")

【讨论】:

根据你的第一个建议,我得到:``` let obj = PyMyStruct("../../dump.spb").new() ^ TypeError: Class constructor PyMyStruct cannot be invoked without ' new' ``` 但是使用 `#[wasm_bindgen(constructor)]` 就可以了。 很遗憾:还有2个问题: 1.对象的方法只能调用一次。第二次你仍然得到 Error: null pointer passing to rust 2. Rust 在 wasm 中运行时无法打开本地文件,即使在 Node.js 中也是如此。 @Matthias 我更新了我的答案以修复您的第一个错误【参考方案2】:

我通过使用 https://neon-bindings.com 而不是将 API 编译为 web-assembly 解决了这个问题。

这里的绑定如下:

use core;
use std::rc::Rc;
use neon::prelude::*;

#[derive(Clone)]
pub struct MyStruct 
    inner: Rc<core::MyStruct>,


declare_types! 
    pub class JsMyStruct for MyStruct 
        init(mut cx) 
            let filename = cx.argument::<JsString>(0)?.value();

            match core::MyStruct::load(&filename) 
                Ok(inner) => return Ok(MyStruct
                    inner: Rc::new(inner)
                ),
                Err(msg) => 
                    panic!("", msg)
                
            
        

        method header(mut cx) 
            let this = cx.this();
            let container = 
                let guard = cx.lock();
                let this = this.borrow(&guard);
                (*this).clone()
            ;
            let keys = container.inner.data.keys().collect::<Vec<_>>();
            let js_array = JsArray::new(&mut cx, keys.len() as u32);
            for (i, obj) in keys.into_iter().enumerate() 
                let js_string = cx.string(obj);
                js_array.set(&mut cx, i as u32, js_string).unwrap();
            
            Ok(js_array.upcast())
        

        method value(mut cx) 
            let key = cx.argument::<JsString>(0)?.value();
            let this = cx.this();
            let container = 
                let guard = cx.lock();
                let this = this.borrow(&guard);
                (*this).clone()
            ;
            if let Some(data) = container.inner.data.get(&key) 
                return Ok(neon_serde::to_value(&mut cx, data)?);
             else 
                panic!("No value for key \"\" available", key);
            
        
    


register_module!(mut m, 
    m.export_class::<JsMyStruct>("MyStruct")?;
    Ok(())
);

【讨论】:

以上是关于使用 wasm-bindgen 对大型 rust 对象进行 Js 绑定的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Emscripten 编译的程序中的 wasm-bindgen?

如何将嵌套 Vecs 与 wasm-bindgen 一起使用?

如何使用 Wasm-Bindgen Web_sys Wasm-pack 将字符串从 Js 传递到通过 Rust 生成的 Wasm

使用 Rust + WebAssembly 编写 crc32

如何在编译为 WebAssembly 的 Rust 库中使用 C 库?

Rust web 前端库/框架评测,以及和 js 前端库/框架的比较