Как я могу освободить все структуры, которые Rust размещает поверх буфера FFI, не освобождая сам буфер? - PullRequest
0 голосов
/ 16 октября 2018

У меня есть Java-программа, которая вызывает Rust через JNA, предоставляя стороне Rust указатель на потенциально большой (выделенный в куче) буфер непрерывно размеченных \ 0 завершенных строк UTF-8.Память принадлежит стороне Java и освобождается, когда сборщик мусора завершает связанный объект.

Моя цель - обработать этот буфер, интерпретируя его как строковый вектор, делая то, что мне нужно, и отбрасывая всеструктуры, которые Rust размещает поверх буфера, например Vec, String s и т. д. Из-за потенциального размера буфера, я хочу избежать копирования данных, если это возможно.

Рассмотрим следующий код:

use std::ffi::CString;
use std::os::raw::c_char;

pub extern "C" fn process_data(data: *const c_char, num_elements: i64) {
    let mut vec: Vec<String> = Vec::with_capacity(num_elements as usize);
    let mut offset = 0;

    unsafe {
        for _ in 0..num_elements {
            let ptr = { data.offset(offset as isize) };

            // Main goal here is to have no memory copy involved
            let s = String::from_utf8_unchecked(CString::from_raw(ptr as *mut c_char).into_bytes());

            offset += s.len() + 1; // Include string termination
            vec.push(s);
        }
    }

    // do stuff with the vector
    // ...

    // Now that we're done, vec would be dropped, freeing the strings, thus freeing their underlying memory.
}

Насколько я понимаю, у меня теперь есть Vec, который внутренне указывает на буфер, содержащий String s, который, в свою очередь, внутренне указывает на Vec s, что затемкаким-то образом указать на буфер, который я передал.

Если я позволю коду работать так, не забывая вектор явно, я получаю двойное освобождение, потому что Java пытается освободить буфер, но Rust уже сделал это,сбросив вектор.Имеет смысл.Однако, если забыть, что вектор пропускает все структуры «управления» поверх буфера.

Я думал о том, как можно освободить все, что Rust выделил, без утечки памяти.Я думал о явной утечке ящиков и отбрасывании указателей, которые они мне дают (потому что у Java все еще есть указатель) вдоль строк:

fn forget_vec(vec: Vec<String>) {
    vec.into_iter().map(|s| {
        Box::into_raw(s.into_bytes().into_boxed_slice());
    }
}

Однако, так как срез также является структурой, которая содержит длину иуказатель, и, делая выше, я думаю, что утечка этой структуры.Я искал что-то, что потребляет ломтик и возвращает мне только указатель, такой как *const u8.

У меня такое ощущение, что я, как правило, иду в правильном направлении, но я пропускаю что-то важное или имеюслишком мало понимания Rust, чтобы заставить его работать напрямую.

1 Ответ

0 голосов
/ 16 октября 2018

Перечитайте документацию для CString, выделение мое:

Тип, представляющий принадлежащую *1008*, C-совместимую строку с нулевым окончанием св середине нет нулевых байтов.

Этот тип предназначен для безопасной генерации C-совместимой строки из фрагмента байта Rust или вектора .

Вы не владеете этими строками, как Java.Используйте &str и CStr вместо:

use std::ffi::CStr;
use std::os::raw::c_char;

pub extern "C" fn process_data(data: *const c_char, num_elements: i64) {
    let mut vec: Vec<&str> = Vec::with_capacity(num_elements as usize);

    unsafe {
        let mut ptr = data;

        for _ in 0..num_elements {
            let s = CStr::from_ptr(ptr);
            ptr = ptr.add(s.to_bytes().len() + 1); // Include string termination

            if let Ok(s) = s.to_str() {
                vec.push(s);
            }
        }
    }
}

Когда ваш Vec отбрасывается, он просто отбрасывает ссылки, и ничто не освобождается, кроме самого Vec.

...