Подтвердить что ты не робот

Как преобразовать строку C в строку Rust и вернуться через FFI?

Я пытаюсь получить строку C, возвращаемую библиотекой C, и преобразовать ее в строку Rust через FFI.

mylib.c

const char* hello(){
    return "Hello World!";
}

main.rs

#![feature(link_args)]

extern crate libc;
use libc::c_char;

#[link_args = "-L . -I . -lmylib"]
extern {
    fn hello() -> *c_char;
}

fn main() {
    //how do I get a str representation of hello() here?
}
4b9b3361

Ответ 1

Лучший способ работать со строками C в Rust - использовать структуры из std::ffi, а именно CStr и CString.

CStr является типом динамического размера и поэтому его можно использовать только с помощью указателя. Это делает его очень похожим на обычный тип str. Вы можете построить &CStr из *const c_char, используя небезопасный CStr::from_ptr статический метод. Этот метод небезопасен, потому что нет гарантии, что необработанный указатель, который вы передаете ему, действителен, что он действительно указывает на допустимую строку C и что время жизни строки верное.

Вы можете получить &str из &CStr, используя метод to_str().

Вот пример:

extern crate libc;

use libc::c_char;
use std::ffi::CStr;
use std::str;

extern {
    fn hello() -> *const c_char;
}

fn main() {
    let c_buf: *const c_char = unsafe { hello() };
    let c_str: &CStr = unsafe { CStr::from_ptr(c_buf) };
    let str_slice: &str = c_str.to_str().unwrap();
    let str_buf: String = str_slice.to_owned();  // if necessary
}

Вам нужно учитывать срок службы ваших указателей *const c_char и кто их владеет. В зависимости от C API вам может потребоваться вызвать специальную функцию освобождения от строки. Вам нужно тщательно организовать конверсии, чтобы срезы не пережить указатель. Тот факт, что CStr::from_ptr возвращает a &CStr с произвольным временем жизни, помогает здесь (хотя он опасен сам по себе); например, вы можете инкапсулировать свою строку C в структуру и предоставить преобразование Deref, чтобы вы могли использовать вашу структуру, как если бы это был строковый фрагмент:

extern crate libc;

use libc::c_char;
use std::ops::Deref;
use std::ffi::CStr;

extern "C" {
    fn hello() -> *const c_char;
    fn goodbye(s: *const c_char);
}

struct Greeting {
    message: *const c_char,
}

impl Drop for Greeting {
    fn drop(&mut self) {
        unsafe {
            goodbye(self.message);
        }
    }
}

impl Greeting {
    fn new() -> Greeting {
        Greeting { message: unsafe { hello() } }
    }
}

impl Deref for Greeting {
    type Target = str;

    fn deref<'a>(&'a self) -> &'a str {
        let c_str = unsafe { CStr::from_ptr(self.message) };
        c_str.to_str().unwrap()
    }
}

В этом модуле есть еще один тип, называемый CString. Он имеет ту же связь с CStr, что и String с str - CString является версией CStr. Это означает, что он "удерживает" дескриптор для распределения байтовых данных, а dropping CString освобождает предоставленную им память (по существу, CString обертывает Vec<u8>, а последний - будет отброшен). Следовательно, это полезно, когда вы хотите выставить данные, выделенные в Rust, как строку C.

К сожалению, строки C всегда заканчиваются нулевым байтом и не могут содержать внутри них, в то время как Rust &[u8]/Vec<u8> - это совершенно противоположное - они не заканчиваются нулевым байтом и могут содержать произвольные числа их внутри. Это означает, что переход от Vec<u8> в CString не является ни безошибочным, ни свободным - конструктор CString проверяет нули внутри предоставленных вами данных, возвращая ошибку, если он найдет какой-то элемент, и добавляет нулевой байт до конца байтового вектора, который может потребовать его перераспределения.

Подобно String, который реализует Deref<Target = str>, CString реализует Deref<Target = CStr>, поэтому вы можете вызывать методы, определенные на CStr, непосредственно на CString. Это важно, потому что метод as_ptr(), который возвращает *const c_char, необходимый для взаимодействия C, определен в CStr. Вы можете вызвать этот метод непосредственно на значения CString, что удобно.

CString может быть создан из всего, что можно преобразовать в Vec<u8>. String, &str, Vec<u8> и &[u8] являются допустимыми аргументами для функции-конструктора, CString::new(). Естественно, если вы передадите байтовый срез или срез строки, будет создано новое распределение, тогда как Vec<u8> или String будет потреблено.

extern crate libc;

use libc::c_char;
use std::ffi::CString;

fn main() {
    let c_str_1 = CString::new("hello").unwrap(); // from a &str, creates a new allocation
    let c_str_2 = CString::new(b"world" as &[u8]).unwrap(); // from a &[u8], creates a new allocation
    let data: Vec<u8> = b"12345678".to_vec(); // from a Vec<u8>, consumes it
    let c_str_3 = CString::new(data).unwrap();

    // and now you can obtain a pointer to a valid zero-terminated string
    // make sure you don't use it after c_str_2 is dropped
    let c_ptr: *const c_char = c_str_2.as_ptr();

    // the following will print an error message because the source data
    // contains zero bytes
    let data: Vec<u8> = vec![1, 2, 3, 0, 4, 5, 0, 6];
    match CString::new(data) {
        Ok(c_str_4) => println!("Got a C string: {:p}", c_str_4.as_ptr()),
        Err(e) => println!("Error getting a C string: {}", e),
    }  
}

Если вам нужно передать право собственности на код CString на C, вы можете вызвать CString.html::into_raw. Затем вам необходимо вернуть указатель и освободить его в Rust; распределитель Rust вряд ли будет таким же, как распределитель, используемый malloc и free. Все, что вам нужно сделать, это вызвать CString::from_raw, а затем разрешить нормальную передачу строки.