rust - 我如何使用 cbindgen 返回并释放一个 Box<Vec<_>>?

标签 rust ffi

我有一个从 Rust 返回到 C 代码的结构。我不知道这是否是做事的好方法,但它确实可以用于重建结构和释放内存而不会泄漏。

#[repr(C)]
pub struct s {
    // ...
}

#[repr(C)]
#[allow(clippy::box_vec)]
pub struct s_arr {
    arr: *const s,
    n: i8,
    vec: Box<Vec<s>>,
}

/// Frees memory that was returned to C code
pub unsafe extern "C" fn free_s_arr(a: *mut s_arr) {
    Box::from_raw(s_arr);
}

/// Generates an array for the C code
pub unsafe extern "C" fn gen_s_arr() -> *mut s_arr {
    let many_s: Vec<s> = Vec::new();
    // ... logic here

    Box::into_raw(Box::new(s_arr {
        arr: many_s.as_mut_ptr(),
        n: many_s.len() as i8,
        vec: many_s,
    }))
}

C 头文件目前是手写的,但我想试试 cbindgen。 s_arr 的手动 C 定义是:

struct s_arr {
    struct s *arr;
    int8_t n;
    void *_;
};

cbindgen 为 s_arr 生成以下内容:

typedef struct Box_Vec_s Box_Vec_s;

typedef struct s_arr {
        const s *arr;
        int8_t n;
        Box_Vec_s vec;
} s_arr;

这不起作用,因为未定义 struct Box_Vec_s。理想情况下,我只想覆盖为 vec 生成的 cbindgen 类型,使其成为 void *,因为它不需要更改代码,因此不需要额外的测试,但我对其他人持开放态度建议。

我已经查看了 cbindgen 文档,但没有找到示例,但找不到任何内容。

最佳答案

你的问题有点不清楚,但我认为如果我理解正确的话,你会混淆两件事并因此被引向黑暗的小巷。

在 C 语言中,一个动态大小的数组,您可能知道,由两件事来标识:

  1. 它的起始位置,作为一个指针
  2. 它的长度

Rust 遵循相同的约定 - a Vec<_> ,在引擎盖下,共享相同的结构(好吧,差不多。它也有容量,但这不是重点)。

在指针顶部 传递盒装矢量不仅矫枉过正,而且极其不明智。 FFI 绑定(bind)可能很智能,但它们在大多数情况下还不够智能,无法处理装箱的复杂类型。

为了解决这个问题,我们将简化您的绑定(bind)。我在 struct S 中添加了一个元素向您展示它是如何工作的。我还清理了您的 FFI 边界:

#[repr(C)]
#[no_mangle]
pub struct S {
    foo: u8
}

#[repr(C)]
pub struct s_arr {
    arr: *mut S,
    n: usize,
    cap: usize
}

// Retrieve the vector back
pub unsafe extern "C" fn recombine_s_arr(ptr: *mut S, n: usize, cap: usize) -> Vec<S> {
    Vec::from_raw_parts(ptr, n, cap)
}

#[no_mangle]
pub unsafe extern "C" fn gen_s_arr() -> s_arr {
    let mut many_s: Vec<S> = Vec::new();

    let output = s_arr {
        arr: many_s.as_mut_ptr(),
        n: many_s.len(),
        cap: many_s.capacity()
    };
    std::mem::forget(many_s);
    output
}

由此,cbindgen 返回预期的 header 定义:

typedef struct {
  uint8_t foo;
} so58311426S;

typedef struct {
  so58311426S *arr;
  uintptr_t n;
  uintptr_t cap;
} so58311426s_arr;

so58311426s_arr gen_s_arr(void);

这让我们可以调用 gen_s_arr()从 C 或 Rust 中检索一个可跨 FFI 边界的两个部分使用的结构 (so58311426s_arr)。这个结构包含了我们能够修改 S 数组所需的一切。 (好吧,so58311426S 根据 cbindgen)。

通过FFI时,您需要确定一些简单的事情:

  • 您不能传递原始框或非原始类型;您几乎普遍需要向下转换为一组指针或更改您的定义以适应(就像我在这里所做的那样)
  • 您大多数绝对不传递原始向量。您最多传递一个切片,因为它是原始类型(请参见上一点)。
  • 你一定要std::mem::forget()无论您不想释放什么,请务必记住释放它或在其他地方重新分配它。

我会在一个小时内编辑这个问题;我有一架飞机要去。让我知道是否有任何需要澄清的地方,一旦我到了正确的国家/地区,我就会着手解决 :-)

关于rust - 我如何使用 cbindgen 返回并释放一个 Box<Vec<_>>?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58311426/

相关文章:

rust - Rust 中的类子类型化

ruby - 无法通过 rvm bundle install 安装 ffi 1.9.0 - OSX 10.8.4

rust - 有什么方法可以创建 const &'static CStr 吗?

rust - FFI基本类型大小

泛型错误 : expected type parameter, 找到结构

generics - 在使用 Option::None 时,有没有办法提示编译器使用某种默认泛型类型?

asynchronous - 当 future 包装它被丢弃时,如何停止运行同步代码?

rust - 尝试使用 serde_json 从 curl 解析 JSON 时类型不匹配

java - 如何从 Java 调用 Haskell

rust - 计算两个原始指针之间的距离