我如何 return pub extern "C" fn 中的动态长度向量?
How do I return an vector of dynamic length in a pub extern "C" fn?
我想 return pub extern "C" fn
中的一个向量。由于向量具有任意长度,我想我需要 return 具有
的结构
向量指针,
向量中元素的个数
我当前的代码是:
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.
中找不到 returning arrays/vectors 的示例
这是从 Rust return 未知大小向量的最佳方式吗?如何修复剩余的编译错误?谢谢!
奖金问题:如果我的向量在结构中的事实改变了答案,也许你也可以展示如果向量不在 Boxed 结构中(我认为这意味着它拥有的向量)如何做到这一点也在堆上)?我想很多查找此 q 的人不会已经将他们的矢量装箱。
Bonus q2:我只 return 向量来查看其值(在 Python 中),但我不想让调用代码更改向量。但我想没有办法让内存只读并确保调用代码不会混淆向量? const
只是为了表明意图,对吧?
Ps:我不太了解 C 或 Rust,所以我的尝试可能完全是 WTF。
有多种方法可以将数组传递给 C。
首先,虽然 C 具有 固定大小数组的概念(int a[5]
具有类型 int[5]
并且 sizeof(a)
将 return 5 * sizeof(int)
), 无法直接将数组传递给函数或从函数中 return 传递数组。
另一方面,可以将固定大小的数组包装在 struct
和 return 中,即 struct
.
此外,当使用数组时,必须初始化所有元素,否则 memcpy
技术上有未定义的行为(因为它正在读取未定义的值)并且 valgrind 肯定会报告该问题。
使用动态数组
动态数组是在编译时长度未知的数组。
如果不知道合理的上限,或者认为此界限太大而无法按值传递,则可以选择 return 动态数组。
有两种方法可以处理这种情况:
- 要求 C 传递一个大小合适的缓冲区
- 分配一个缓冲区并return它给C
它们在谁分配内存方面有所不同:前者更简单,但可能需要有一种方法来提示合适的大小,或者如果大小证明不合适,则能够"rewind"。
要求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 初始化它或要求客户这样做)。
分配一个缓冲区并return它到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 中,所有元素都应该被初始化,即使未使用也是如此;将它们归零效果很好。
与动态情况类似,它可以通过可变指针传递或return按值编辑。
// file.h
struct FixedArray {
int32_t array[32];
};
// file.rs
#[repr(C)]
struct FixedArray {
array: [libc::int32_t; 32],
}
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 通常不会 "like" returning 这样的特殊大小的数组结构。更常见的 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
函数很简单,对return数组,只需做
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 用户负责销毁该缓冲区。它还可以防止数组的多个可变副本同时浮动(假设您没有实现 return 指针的版本)。
请注意,您也可以将数组 "move" 排序到 C 中,但不特别推荐这样做,并且涉及使用 mem::forget
并期望 C 用户显式调用销毁函数,因为以及要求您和用户都遵守一些可能难以构建程序的纪律。
如果你想从C接收一个数组,你基本上只需要一个*mut i32
和i32
对应于缓冲区的开始和长度.您可以使用 from_raw_parts
函数将其 assemble 放入切片中,然后使用 to_vec
函数创建一个拥有的 Vector,其中包含从 Rust 端分配的值。如果您不打算拥有这些值,则可以简单地传递通过 from_raw_parts
.
生成的切片
但是,必须从任何一侧初始化所有值,通常为零。否则,您会调用合法的未定义行为,这通常会导致分段错误(当使用 GDB 检查时,这些错误往往会令人沮丧地消失)。
我想 return pub extern "C" fn
中的一个向量。由于向量具有任意长度,我想我需要 return 具有
向量指针,
向量中元素的个数
我当前的代码是:
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.
中找不到 returning arrays/vectors 的示例这是从 Rust return 未知大小向量的最佳方式吗?如何修复剩余的编译错误?谢谢!
奖金问题:如果我的向量在结构中的事实改变了答案,也许你也可以展示如果向量不在 Boxed 结构中(我认为这意味着它拥有的向量)如何做到这一点也在堆上)?我想很多查找此 q 的人不会已经将他们的矢量装箱。
Bonus q2:我只 return 向量来查看其值(在 Python 中),但我不想让调用代码更改向量。但我想没有办法让内存只读并确保调用代码不会混淆向量? const
只是为了表明意图,对吧?
Ps:我不太了解 C 或 Rust,所以我的尝试可能完全是 WTF。
有多种方法可以将数组传递给 C。
首先,虽然 C 具有 固定大小数组的概念(int a[5]
具有类型 int[5]
并且 sizeof(a)
将 return 5 * sizeof(int)
), 无法直接将数组传递给函数或从函数中 return 传递数组。
另一方面,可以将固定大小的数组包装在 struct
和 return 中,即 struct
.
此外,当使用数组时,必须初始化所有元素,否则 memcpy
技术上有未定义的行为(因为它正在读取未定义的值)并且 valgrind 肯定会报告该问题。
使用动态数组
动态数组是在编译时长度未知的数组。
如果不知道合理的上限,或者认为此界限太大而无法按值传递,则可以选择 return 动态数组。
有两种方法可以处理这种情况:
- 要求 C 传递一个大小合适的缓冲区
- 分配一个缓冲区并return它给C
它们在谁分配内存方面有所不同:前者更简单,但可能需要有一种方法来提示合适的大小,或者如果大小证明不合适,则能够"rewind"。
要求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 初始化它或要求客户这样做)。
分配一个缓冲区并return它到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 中,所有元素都应该被初始化,即使未使用也是如此;将它们归零效果很好。
与动态情况类似,它可以通过可变指针传递或return按值编辑。
// file.h
struct FixedArray {
int32_t array[32];
};
// file.rs
#[repr(C)]
struct FixedArray {
array: [libc::int32_t; 32],
}
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 通常不会 "like" returning 这样的特殊大小的数组结构。更常见的 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
函数很简单,对return数组,只需做
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 用户负责销毁该缓冲区。它还可以防止数组的多个可变副本同时浮动(假设您没有实现 return 指针的版本)。
请注意,您也可以将数组 "move" 排序到 C 中,但不特别推荐这样做,并且涉及使用 mem::forget
并期望 C 用户显式调用销毁函数,因为以及要求您和用户都遵守一些可能难以构建程序的纪律。
如果你想从C接收一个数组,你基本上只需要一个*mut i32
和i32
对应于缓冲区的开始和长度.您可以使用 from_raw_parts
函数将其 assemble 放入切片中,然后使用 to_vec
函数创建一个拥有的 Vector,其中包含从 Rust 端分配的值。如果您不打算拥有这些值,则可以简单地传递通过 from_raw_parts
.
但是,必须从任何一侧初始化所有值,通常为零。否则,您会调用合法的未定义行为,这通常会导致分段错误(当使用 GDB 检查时,这些错误往往会令人沮丧地消失)。