簡體   English   中英

如何在發布外部“ C” fn中返回動態長度的向量?

[英]How do I return an vector of dynamic length in a pub extern “C” fn?

我想在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>,
}

我得到的錯誤是

$ 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返回未知大小的向量的最佳方法嗎? 如何解決剩余的編譯錯誤? 謝謝!

獎勵q:如果我的向量在結構中的事實改變了答案,也許您還可以顯示如果向量尚未在Boxed結構中的話,該如何做(我認為這也意味着它擁有的向量也在堆中) )? 我猜很多人在查詢這個q時都不會將其向量裝箱。

獎勵q2:我只返回向量以查看其值(在Python中),但我不想讓調用代碼更改向量。 但是我想沒有辦法將內存設為只讀,並確保調用代碼不會與向量混淆嗎? const僅用於顯示意圖,對嗎?

附:我不太了解C或Rust,所以我的嘗試可能完全是WTF。

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常常不喜歡這樣返回特定大小的數組結構。 更常見的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用戶負責銷毀該緩沖區。 它還可以防止數組的多個可變副本同時浮動(假設您未實現返回指針的版本)。

請注意,您也可以將數組“移動”到C中,但是不建議這樣做,它涉及使用mem::forget並期望C用戶顯式調用銷毀函數,同時要求您和用戶遵守一些可能很難圍繞程序進行構造的紀律。

如果要從C 接收數組,則實際上只需要輸入*mut i32i32 *mut i32i32對應於緩沖區的開始和長度。 您可以使用from_raw_parts函數將其組合成一個切片,然后使用to_vec函數創建一個擁有的Vector,其中包含從Rust側分配的值。 如果您不打算擁有這些值,則可以簡單地通過from_raw_parts傳遞您生成的切片。

但是,必須從任一側初始化所有值,通常初始化為零。 否則,您將調用合法的未定義行為,這通常會導致分段錯誤(在使用GDB進行檢查時,這些錯誤通常會令人沮喪地消失)。

有多種將數組傳遞給C的方法。


首先,盡管C 具有固定大小數組的概念( int a[5]具有int[5]類型,並且sizeof(a)將返回5 * sizeof(int) ),但無法直接傳遞數組到函數或從中返回數組。

另一方面,可以將固定大小的數組包裝在struct並返回該struct

此外,在使用數組時,必須初始化所有元素,否則, memcpy技術上具有未定義的行為(因為它正在從未定義的值讀取),並且valgrind肯定會報告該問題。


使用動態數組

動態數組是在編譯時長度未知的數組。

如果不知道合理的上限,或者認為該界限太大而無法按值傳遞,則可以選擇返回動態數組。

有兩種方法可以處理這種情況:

  • 要求C傳遞適當大小的緩沖區
  • 分配一個緩沖區並將其返回給C

它們在分配內存的人方面有所不同:前者比較簡單,但可能需要提示一種合適的大小,或者在大小不合適的情況下“倒帶”。

要求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],
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM