从 JS 导入类
就像我们开始导出函数后一样,我们也希望能够导入!既然我们已经向 JS 导出了一个 class
,我们也希望能够在 Rust 中导入类以调用方法等等。由于 JS 类通常只是 JS 对象,这里的绑定看起来与上面描述的 JS 对象绑定非常相似。
但像往常一样,让我们深入一个示例!
#![allow(unused)] fn main() { #[wasm_bindgen(module = "./bar")] extern "C" { type Bar; #[wasm_bindgen(constructor)] fn new(arg: i32) -> Bar; #[wasm_bindgen(js_namespace = Bar)] fn another_function() -> i32; #[wasm_bindgen(method)] fn get(this: &Bar) -> i32; #[wasm_bindgen(method)] fn set(this: &Bar, val: i32); #[wasm_bindgen(method, getter)] fn property(this: &Bar) -> i32; #[wasm_bindgen(method, setter)] fn set_property(this: &Bar, val: i32); } fn run() { let bar = Bar::new(Bar::another_function()); let x = bar.get(); bar.set(x + 3); bar.set_property(bar.property() + 6); } }
与我们之前的导入不同,这个导入有点啰嗦!请记住,wasm-bindgen
的目标之一是尽可能使用原生 Rust 语法,因此这主要是为了使用 #[wasm_bindgen]
属性来解释 Rust 中写下的内容。现在这里有一些属性注解,让我们逐一介绍一下
#[wasm_bindgen(module = "./bar")]
- 在导入中看到过,这是声明所有后续功能从何处导入的。例如,Bar
类型将从./bar
模块导入。type Bar
- 这是声明 JS 类为 Rust 中的新类型。这意味着将生成一个新的类型Bar
,它是“不透明的”,但在内部表示为包含JsValue
。我们稍后会看到更多关于这方面的内容。#[wasm_bindgen(constructor)]
- 这表示绑定的名称实际上没有在 JS 中使用,而是转换为new Bar()
。此函数的返回值必须是一个裸类型,例如Bar
。#[wasm_bindgen(js_namespace = Bar)]
- 此属性表示函数声明通过 JS 中的Bar
类进行命名空间划分。#[wasm_bindgen(static_method_of = SomeJsClass)]
- 此属性与js_namespace
类似,但不是生成一个自由函数,而是生成SomeJsClass
的静态方法。#[wasm_bindgen(method)]
- 最后,此属性表示将发生方法调用。第一个参数必须是一个 JS 结构体,例如Bar
,并且 JS 中的调用看起来像Bar.prototype.set.call(...)
。
考虑到所有这些,让我们看一下生成的 JS。
import * as wasm from './foo_bg';
import { Bar } from './bar';
// other support functions omitted...
export function __wbg_s_Bar_new() {
return addHeapObject(new Bar());
}
const another_function_shim = Bar.another_function;
export function __wbg_s_Bar_another_function() {
return another_function_shim();
}
const get_shim = Bar.prototype.get;
export function __wbg_s_Bar_get(ptr) {
return shim.call(getObject(ptr));
}
const set_shim = Bar.prototype.set;
export function __wbg_s_Bar_set(ptr, arg0) {
set_shim.call(getObject(ptr), arg0)
}
const property_shim = Object.getOwnPropertyDescriptor(Bar.prototype, 'property').get;
export function __wbg_s_Bar_property(ptr) {
return property_shim.call(getObject(ptr));
}
const set_property_shim = Object.getOwnPropertyDescriptor(Bar.prototype, 'property').set;
export function __wbg_s_Bar_set_property(ptr, arg0) {
set_property_shim.call(getObject(ptr), arg0)
}
就像从 JS 导入函数时一样,我们可以看到为所有相关函数生成了一堆 shim。 new
静态函数具有 #[wasm_bindgen(constructor)]
属性,这意味着它应该调用 new
构造函数(正如我们在这里看到的那样),而不是任何特定的方法。然而,静态函数 another_function
被分派为 Bar.another_function
。
get
和 set
函数是方法,因此它们通过 Bar.prototype
,否则它们的第一个参数隐式地是 JS 对象本身,该对象通过 getObject
加载,就像我们之前看到的那样。
在 Rust 方面开始出现一些真正的内容,让我们来看看
#![allow(unused)] fn main() { pub struct Bar { obj: JsValue, } impl Bar { fn new() -> Bar { extern "C" { fn __wbg_s_Bar_new() -> u32; } unsafe { let ret = __wbg_s_Bar_new(); Bar { obj: JsValue::__from_idx(ret) } } } fn another_function() -> i32 { extern "C" { fn __wbg_s_Bar_another_function() -> i32; } unsafe { __wbg_s_Bar_another_function() } } fn get(&self) -> i32 { extern "C" { fn __wbg_s_Bar_get(ptr: u32) -> i32; } unsafe { let ptr = self.obj.__get_idx(); let ret = __wbg_s_Bar_get(ptr); return ret } } fn set(&self, val: i32) { extern "C" { fn __wbg_s_Bar_set(ptr: u32, val: i32); } unsafe { let ptr = self.obj.__get_idx(); __wbg_s_Bar_set(ptr, val); } } fn property(&self) -> i32 { extern "C" { fn __wbg_s_Bar_property(ptr: u32) -> i32; } unsafe { let ptr = self.obj.__get_idx(); let ret = __wbg_s_Bar_property(ptr); return ret } } fn set_property(&self, val: i32) { extern "C" { fn __wbg_s_Bar_set_property(ptr: u32, val: i32); } unsafe { let ptr = self.obj.__get_idx(); __wbg_s_Bar_set_property(ptr, val); } } } impl WasmBoundary for Bar { // ... } impl ToRefWasmBoundary for Bar { // ... } }
在 Rust 中,我们看到为此类导入生成了一个新类型 Bar
。类型 Bar
在内部包含一个 JsValue
,因为 Bar
的实例旨在表示存储在我们模块的堆栈/slab 中的 JS 对象。然后,它的工作方式与我们最初看到的 JS 对象的工作方式大致相同。
当调用 Bar::new
时,我们将获得一个索引,该索引被包装在 Bar
中(它本身只是一个被剥离时的内存中的 u32
)。然后,每个函数都将索引作为第一个参数传递,否则在 Rust 中转发所有内容。