我想在pub extern "C" fn
中返回一个向量。由于向量具有任意长度,我想我需要返回带
指向矢量的指针,
向量中的元素数
我目前的代码是:
extern crate libc;
use self::libc::{size_t, int32_t, int64_t};
// struct to represent an array and its size
#[repr(C)]
pub struct array_and_size {
values: int64_t, // this is probably not how you denote a pointer, right?
size: int32_t,
}
// The vector I want to return the address of is already in a Boxed struct,
// which I have a pointer to, so I guess the vector is on the heap already.
// Dunno if this changes/simplifies anything?
#[no_mangle]
pub extern "C" fn rle_show_values(ptr: *mut Rle) -> array_and_size {
let rle = unsafe {
assert!(!ptr.is_null());
&mut *ptr
};
// this is the Vec<i32> I want to return
// the address and length of
let values = rle.values;
let length = values.len();
array_and_size {
values: Box::into_raw(Box::new(values)),
size: length as i32,
}
}
#[derive(Debug, PartialEq)]
pub struct Rle {
pub values: Vec<i32>,
}
我得到的错误是
$ cargo test
Compiling ranges v0.1.0 (file:///Users/users/havpryd/code/rust-ranges)
error[E0308]: mismatched types
--> src/rle.rs:52:17
|
52 | values: Box::into_raw(Box::new(values)),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected i64, found *-ptr
|
= note: expected type `i64`
= note: found type `*mut std::vec::Vec<i32>`
error: aborting due to previous error
error: Could not compile `ranges`.
To learn more, run the command again with --verbose.
-> exit code: 101
我发布了整个内容,因为我找不到在非常有用的Rust FFI Omnibus中返回数组/向量的示例。
这是从Rust返回未知大小的矢量的最佳方法吗?如何修复剩余的编译错误?谢谢!
Bonus q:如果我的向量在一个结构中的事实改变了答案,也许你也可以展示如果向量不在Boxed结构中的话怎么做(我认为它意味着它所拥有的向量是堆也)?我想很多人看这个q都不会把他们的矢量装箱了。
Bonus q2:我只返回向量来查看它的值(在Python中),但我不想让调用代码改变向量。但我想有没有办法让内存只读,并确保调用代码不会篡改向量? const
只是为了显示意图,对吧?
Ps:我不太了解C或Rust,所以我的尝试可能完全是WTF。
答案 0 :(得分:5)
pub struct array_and_size {
values: int64_t, // this is probably not how you denote a pointer, right?
size: int32_t,
}
首先,你是正确的。 values
所需的类型为*mut int32_t
。
一般来说,并注意到有各种各样的C编码风格,C通常不会&#34;喜欢&#34;返回像这样的ad-hoc大小的数组结构。更常见的C API是
int32_t rle_values_size(RLE *rle);
int32_t *rle_values(RLE *rle);
(注意:许多内部程序实际上使用大小的数组结构,但这是目前面向用户库最常见的,因为它自动与C中表示数组的最基本方式兼容)。
在Rust中,这将转化为:
extern "C" fn rle_values_size(rle: *mut RLE) -> int32_t
extern "C" fn rle_values(rle: *mut RLE) -> *mut int32_t
size
函数很简单,只需执行
extern "C" fn rle_values(rle: *mut RLE) -> *mut int32_t {
unsafe { &mut (*rle).values[0] }
}
这给出了一个指向Vec
底层缓冲区的第一个元素的原始指针,它实际上都是C风格的数组。
如果您希望提供 C数据,而不是给C引用您的数据,最常见的选择是允许用户传入您将数据克隆到的缓冲区:
extern "C" fn rle_values_buf(rle: *mut RLE, buf: *mut int32_t, len: int32_t) {
use std::{slice,ptr}
unsafe {
// Make sure we don't overrun our buffer's length
if len > (*rle).values.len() {
len = (*rle).values.len()
}
ptr::copy_nonoverlapping(&(*rle).values[0], buf, len as usize);
}
}
其中,来自C,看起来像
void rle_values_buf(RLE *rle, int32_t *buf, int32_t len);
这(浅)将您的数据复制到可能是C分配的缓冲区中,然后由C用户负责销毁。它还可以防止阵列的多个可变副本同时浮动(假设您没有实现返回指针的版本)。
请注意,您可以选择&#34;移动&#34;数组也是C,但并不特别推荐使用mem::forget
并期望C用户显式调用销毁函数,同时要求你和用户遵守某些规则这可能很难构建程序。
如果你想从C中接收一个数组,你基本上只需要一个与缓冲区起始和长度相对应的*mut i32
和i32
。您可以使用from_raw_parts
函数将其组合到切片中,然后使用to_vec
函数创建一个包含从Rust端分配的值的拥有Vector。如果您没有计划需要拥有这些值,则只需通过from_raw_parts
传递您生成的切片。
但是,必须从任一侧初始化所有值,通常为零。否则,您会调用合法的未定义行为,这通常会导致分段错误(在使用GDB检查时,这会导致令人沮丧的消失)。
答案 1 :(得分:4)
将数组传递给C有多种方法。
首先,虽然C 具有固定大小数组的概念(int a[5]
类型为int[5]
,sizeof(a)
将返回5 * sizeof(int)
),无法直接将数组传递给函数或从中返回数组。
另一方面,可以在struct
中包装固定大小的数组并返回struct
。
此外,在使用数组时,必须初始化所有元素,否则memcpy
在技术上具有未定义的行为(因为它从未定义的值读取)并且valgrind肯定会报告该问题。
使用动态数组
动态数组是一个在编译时长度未知的数组。
如果没有合理的上限,可以选择返回一个动态数组,或者这个边界被认为太大而无法通过值传递。
有两种方法可以解决这种情况:
他们在分配内存方面有所不同:前者更简单,但可能需要有一种方法暗示适当的大小或能够“倒带”#34;如果尺寸证明不合适。
要求C传递合适大小的缓冲区
// file.h
int rust_func(int32_t* buffer, size_t buffer_length);
// file.rs
#[no_mangle]
pub extern fn rust_func(buffer: *mut libc::int32_t, buffer_length: libc::size_t) -> libc::c_int {
// your code here
}
注意std::slice::from_raw_parts_mut
是否存在将此指针+长度转换为可变切片(在将其作为切片或请求客户端之前,先将其初始化为0)。
分配缓冲区并将其返回到C
// file.h
struct DynArray {
int32_t* array;
size_t length;
}
DynArray rust_alloc();
void rust_free(DynArray);
// file.rs
#[repr(C)]
struct DynArray {
array: *mut libc::int32_t,
length: libc::size_t,
}
#[no_mangle]
pub extern fn rust_alloc() -> DynArray {
let mut v: Vec<i32> = vec!(...);
let result = DynArray {
array: v.as_mut_ptr(),
length: v.len() as _,
};
std::mem::forget(v);
result
}
#[no_mangle]
pub extern fn rust_free(array: DynArray) {
if !array.array.is_null() {
unsafe { Box::from_raw(array.array); }
}
}
使用固定大小的数组
类似地,可以使用包含固定大小数组的struct
。请注意,在Rust和C中,所有元素都应该初始化,即使未使用;将它们归零效果很好。
与动态情况类似,它可以通过可变指针传递,也可以通过值返回。
// file.h
struct FixedArray {
int32_t array[32];
};
// file.rs
#[repr(C)]
struct FixedArray {
array: [libc::int32_t; 32],
}