如何在pub extern“C”fn中返回动态长度的向量?

时间:2016-10-20 13:56:51

标签: rust ffi

我想在pub extern "C" fn中返回一个向量。由于向量具有任意长度,我想我需要返回带

的结构
  1. 指向矢量的指针,

  2. 向量中的元素数

  3. 我目前的代码是:

    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。

2 个答案:

答案 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 i32i32。您可以使用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肯定会报告该问题。

使用动态数组

动态数组是一个在编译时长度未知的数组。

如果没有合理的上限,可以选择返回一个动态数组,或者这个边界被认为太大而无法通过值传递。

有两种方法可以解决这种情况:

  • 要求C传递适当大小的缓冲区
  • 分配缓冲区并将其返回到C

他们在分配内存方面有所不同:前者更简单,但可能需要有一种方法暗示适当的大小或能够“倒带”#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],
}