使用 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