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

The*_*Cat 5 ffi rust

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

  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>,
}
Run Code Online (Sandbox Code Playgroud)

我得到的错误是

$ 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
Run Code Online (Sandbox Code Playgroud)

我发布了整个事情,因为我找不到在非常有用的Rust FFI Omnibus中返回数组/向量的示例.

这是从Rust返回未知大小的矢量的最佳方法吗?如何修复剩余的编译错误?谢谢!

Bonus q:如果我的vector在struct中的事实改变了答案,也许你也可以展示如果向量不在Boxed结构中的话怎么做(我认为它所拥有的向量也在堆上) )?我想很多人看这个q都不会把他们的矢量装箱了.

奖励q2:我只返回向量来查看其值(在Python中),但我不想让调用代码更改向量.但我想有没有办法让内存只读,并确保调用代码不会篡改向量?const只是出于表达意图,对吗?

Ps:我不太了解C或Rust,所以我的尝试可能完全是WTF.

Lin*_*ope 6

pub struct array_and_size {
    values: int64_t, // this is probably not how you denote a pointer, right?
    size: int32_t,
}
Run Code Online (Sandbox Code Playgroud)

首先,你是对的.你想要的类型values*mut int32_t.

一般来说,并注意到有各种各样的C编码样式,C通常不会"喜欢"返回像这样的ad-hoc大小的数组结构.更常见的C API是

int32_t rle_values_size(RLE *rle);
int32_t *rle_values(RLE *rle);
Run Code Online (Sandbox Code Playgroud)

(注意:许多内部程序实际上使用大小的数组结构,但这是面向用户库最常见的,因为它自动与C中表示数组的最基本方式兼容).

在Rust中,这将转换为:

extern "C" fn rle_values_size(rle: *mut RLE) -> int32_t
extern "C" fn rle_values(rle: *mut RLE) -> *mut int32_t
Run Code Online (Sandbox Code Playgroud)

size函数很简单,只需返回数组即可

extern "C" fn rle_values(rle: *mut RLE) -> *mut int32_t {
    unsafe { &mut (*rle).values[0] }
}
Run Code Online (Sandbox Code Playgroud)

这给出了一个指向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);
    }
}
Run Code Online (Sandbox Code Playgroud)

从C看起来像

void rle_values_buf(RLE *rle, int32_t *buf, int32_t len);
Run Code Online (Sandbox Code Playgroud)

这(浅)将您的数据复制到可能是C分配的缓冲区中,然后由C用户负责销毁.它还可以防止数组的多个可变副本同时浮动(假设您没有实现返回指针的版本).

请注意,您也可以将数组"移动"到C中,但不特别推荐使用并且需要使用mem::forget并期望C用户显式调用销毁函数,并要求您和用户遵守某些规则这可能很难构建程序.

如果你想从C 接收一个数组,你基本上只需要一个*mut i32i32对应的缓冲区起始和长度.您可以使用该from_raw_parts函数将其组合成一个切片,然后使用该to_vec函数创建一个包含从Rust端分配的值的拥有Vector.如果您不打算需要拥有这些值,您可以简单地传递您生成的切片from_raw_parts.

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