我如何使用 cbindgen 来 return 并释放一个 Box<Vec<_>>?
How do I use cbindgen to return and free a Box<Vec<_>>?
我有一个从 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 中,dynamically-sized 数组,您可能知道,由两件事标识:
- 它的起始位置,作为一个指针
- 它的长度
Rust 遵循相同的约定 - Vec<_>
,在引擎盖下,共享相同的结构(好吧,几乎。它也有容量,但这不是重点)。
将装箱向量 传递到指针顶部 不仅矫枉过正,而且极其不明智。 FFI 绑定可能很聪明,但它们在大多数情况下还不够聪明,无法处理装箱的复杂类型。
为了解决这个问题,我们将简化您的绑定。我在 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 returns 预期的 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);
这允许我们从 C 或 Rust 调用 gen_s_arr()
并检索可跨 FFI 边界的两个部分使用的结构 (so58311426s_arr
)。这个结构包含了我们能够修改 S
数组所需的所有内容(好吧,根据 cbindgen,so58311426S
)。
通过FFI时,您需要确定一些简单的事情:
- 您不能传递原始框或 non-primitive 类型;您几乎普遍需要向下转换为一组指针或更改您的定义以适应(就像我在这里所做的那样)
- 你大多数绝对不传递原始向量。至多,你传递一个切片,因为它是一个原始类型(见上一点)。
- 你一定要
std::mem::forget()
任何你不想释放的东西,一定要记得释放它或在其他地方重新分配它。
我将在一个小时内编辑这个问题;我有一架飞机要去。让我知道是否有任何需要澄清的地方,一旦我到了正确的国家/地区,我就会着手解决:-)
我有一个从 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 中,dynamically-sized 数组,您可能知道,由两件事标识:
- 它的起始位置,作为一个指针
- 它的长度
Rust 遵循相同的约定 - Vec<_>
,在引擎盖下,共享相同的结构(好吧,几乎。它也有容量,但这不是重点)。
将装箱向量 传递到指针顶部 不仅矫枉过正,而且极其不明智。 FFI 绑定可能很聪明,但它们在大多数情况下还不够聪明,无法处理装箱的复杂类型。
为了解决这个问题,我们将简化您的绑定。我在 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 returns 预期的 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);
这允许我们从 C 或 Rust 调用 gen_s_arr()
并检索可跨 FFI 边界的两个部分使用的结构 (so58311426s_arr
)。这个结构包含了我们能够修改 S
数组所需的所有内容(好吧,根据 cbindgen,so58311426S
)。
通过FFI时,您需要确定一些简单的事情:
- 您不能传递原始框或 non-primitive 类型;您几乎普遍需要向下转换为一组指针或更改您的定义以适应(就像我在这里所做的那样)
- 你大多数绝对不传递原始向量。至多,你传递一个切片,因为它是一个原始类型(见上一点)。
- 你一定要
std::mem::forget()
任何你不想释放的东西,一定要记得释放它或在其他地方重新分配它。
我将在一个小时内编辑这个问题;我有一架飞机要去。让我知道是否有任何需要澄清的地方,一旦我到了正确的国家/地区,我就会着手解决:-)