我如何 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 具有

的结构
  1. 向量指针,

  2. 向量中元素的个数

我当前的代码是:

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 i32i32对应于缓冲区的开始和长度.您可以使用 from_raw_parts 函数将其 assemble 放入切片中,然后使用 to_vec 函数创建一个拥有的 Vector,其中包含从 Rust 端分配的值。如果您不打算拥有这些值,则可以简单地传递通过 from_raw_parts.

生成的切片

但是,必须从任何一侧初始化所有值,通常为零。否则,您会调用合法的未定义行为,这通常会导致分段错误(当使用 GDB 检查时,这些错误往往会令人沮丧地消失)。