Как вернуть вектор динамической длины в пабе 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: если тот факт, что мой вектор находится в структуре, меняет ответ, возможно, вы могли бы также показать, как это сделать, если вектор еще не был в структуре в штучной упаковке (что, я думаю, означает, что принадлежащий ему вектор находится в куче))? Я полагаю, что многим людям, ищущим этот q, векторы уже не будут упакованы.

Бонус q2: я возвращаю вектор только для просмотра его значений (в Python), но я не хочу, чтобы вызывающий код изменял вектор. Но я думаю, что нет способа сделать память доступной только для чтения и убедиться, что вызывающий код не смешивается с вектором? const только для того, чтобы показать намерение, верно?

PS: Я плохо знаю C или Rust, поэтому моя попытка может быть полностью WTF.

2 ответа

Решение
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 i32 а также i32 соответствующий началу и длине буфера. Вы можете собрать это в срез, используя from_raw_parts функции, а затем используйте to_vec функция для создания собственного вектора, содержащего значения, выделенные со стороны Rust. Если вы не планируете владеть значениями, вы можете просто передать срез, который вы создали с помощью from_raw_parts,

Однако обязательно, чтобы все значения были инициализированы с любой стороны, как правило, к нулю. В противном случае вы вызываете законно неопределенное поведение, которое часто приводит к ошибкам сегментации (которые, как правило, исчезают при проверке с помощью GDB).

Есть несколько способов передать массив в C.


Прежде всего, в то время как C имеет концепцию массивов фиксированного размера (int a[5] имеет тип int[5] а также sizeof(a) вернусь 5 * sizeof(int)), невозможно напрямую передать массив в функцию или вернуть массив из нее.

С другой стороны, можно обернуть массив фиксированного размера в struct и вернуть это struct,

Кроме того, при использовании массива все элементы должны быть инициализированы, в противном случае memcpy технически имеет неопределенное поведение (так как читает из неопределенных значений), и valgrind определенно сообщит о проблеме.


Использование динамического массива

Динамический массив - это массив, длина которого неизвестна во время компиляции.

Можно выбрать возвращение динамического массива, если разумная верхняя граница не известна или эта граница считается слишком большой для передачи по значению.

Есть два способа справиться с этой ситуацией:

  • попросить С передать буфер подходящего размера
  • выделить буфер и вернуть его в 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],
}
Другие вопросы по тегам