如何将一片 trait 对象传递给 C

问题描述

我最近一直在研究一个库,我想为它制作 C 绑定。这个库中的一个结构体向用户返回了一个 trait 对象。这是函数定义的样子:

pub fn controllers(&self) -> &[Box<dyn Controller>] {
    &self.controllers
}

我不知道如何翻译这个。 C 代码不会插入这些动态对象中。它只会将它们传递回 Rust 代码,即

pub fn controller_get_name(controller: *const dyn Controller) -> *const c_char {
    let controller = controller.as_ref();
    controller.get_name().as_ptr();

}

目前,我有这个:

#[no_mangle]
pub extern "C" fn libvibrant_instance_get_controllers(instance: *mut Instance,mut controllers: *const dyn Controller,len: *mut usize) {
    assert!(!instance.is_null());
    assert!(!len.is_null());

    let instance = unsafe { instance.as_ref().unwrap() };
    controllers = instance.controllers().as_ptr();
    unsafe {
        *len = instance.controllers().len();
    }
}

但显然,这不起作用,因为 as_ptr 返回的是 *const Box<dyn Controller> 而不是 *const dyn Controller。我可以在这里做什么?

解决方法

在编写 C 绑定时,应该从 C 端考虑,要提供哪些操作,尽可能隐藏实现。

例如,您说 C 代码将使用 trait 对象回调 Rust;似乎您想将它们作为(指针,长度)对传递给 C,以便 C 可以遍历它们。然而,这意味着 C 需要知道每个 trait 对象有多大(比单个指针还大;对于共享切片也是如此)。

相反,我会为集合和相关函数提供一个不透明的句柄来迭代它。只有当性能非常重要时,我才会让 C 知道每个元素的大小(这意味着 ABI 将取决于 Rust 表示 DST 的方式)。

有关技术细节,请查看类似 What is a “fat pointer” in Rust? 的问题。