Rust 中的 XPCOM 组件

XPCOM 组件可以用 Rust 编写。

一个简单的例子

以下示例展示了一个实现 nsIObserver 的新类型。

首先,创建一个新的空 crate(例如,使用 cargo init --lib),并在其 Cargo.toml 文件中添加以下依赖项。

[dependencies]
libc = "0.2"
nserror = { path = "../../../xpcom/rust/nserror" }
nsstring = { path = "../../../xpcom/rust/nsstring" }
xpcom = { path = "../../../xpcom/rust/xpcom" }

../ 出现的次数取决于 crate 在文件层次结构中的深度。)

接下来,根据 构建文档将其挂接到构建系统中。

Rust 代码需要导入一些基本类型。 xpcom::interfaces 包含所有常用的 nsI 接口。

use libc::c_char;
use nserror::nsresult;
use std::sync::atomic::{AtomicBool, Ordering};
use xpcom::{interfaces::nsISupports, RefPtr};

下一部分声明了实现。

#[xpcom(implement(nsIObserver), atomic)]
struct MyObserver {
    ran: AtomicBool,
}

这定义了实现类型,该类型将以指定的方式进行引用计数并实现列出的 xpidl 接口。它还将声明第二个初始化器结构体 InitMyObserver,该结构体可用于使用 MyObserver::allocate 方法分配新的 MyObserver

接下来,所有接口方法都在 impl 块中声明为 unsafe 方法。

impl MyObserver {
    #[allow(non_snake_case)]
    unsafe fn Observe(
        &self,
        _subject: *const nsISupports,
        _topic: *const c_char,
        _data: *const u16,
    ) -> nsresult {
        self.ran.store(true, Ordering::SeqCst);
        nserror::NS_OK
    }
}

这些方法始终采用 &self,而不是 &mut self,因此我们需要使用内部可变性:AtomicBoolRefCellCell 等。这是因为所有 XPCOM 对象都是引用计数的(如 Arc<T>),因此无法提供独占访问。

XPCOM 方法默认情况下是不安全的,但可以使用 xpcom_method! 宏来清理它。它还负责空值检查和隐藏引用后面的指针,允许您返回 Result 而不是 nsresult, 等等。

要在 Rust 代码中使用此类型,请执行以下操作。

let observer = MyObserver::allocate(InitMyObserver {
  ran: AtomicBool::new(false),
});
let rv = unsafe {
  observer.Observe(x.coerce(),
                   cstr!("some-topic").as_ptr(),
                   ptr::null())
};
assert!(rv.succeeded());

该实现具有一个(自动生成的)allocate 方法,该方法接收初始化结构体并返回对实例的 RefPtr

coerce 将任何 XPCOM 对象转换为其基接口之一;在本例中,基接口为 nsISupports。在 C++ 中,这将通过继承自动处理,但 Rust 没有继承,因此转换必须是显式的。

更复杂的例子

以下 XPCOM 组件是用 Rust 编写的。