c ffi rust

¿Cómo convierto una cadena C en una cadena Rust y la regreso a través de FFI?



(2)

Estoy tratando de obtener una cadena C devuelta por una biblioteca C y convertirla en una cadena Rust a través de 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? }


Además de lo que ha dicho @ vladimir-matveev, también puede convertir entre ellos sin la ayuda de CStr o CString :

#![feature(link_args)] extern crate libc; use libc::{c_char, puts, strlen}; use std::{slice, str}; #[link_args = "-L . -I . -lmylib"] extern "C" { fn hello() -> *const c_char; } fn main() { //converting a C string into a Rust string: let s = unsafe { let c_s = hello(); str::from_utf8_unchecked(slice::from_raw_parts(c_s as *const u8, strlen(c_s)+1)) }; println!("s == {:?}", s); //and back: unsafe { puts(s.as_ptr() as *const c_char); } }

Solo asegúrate de que al convertir de una cadena & str a una cadena C, tu & str termine con ''/0'' . Tenga en cuenta que en el código anterior utilizo strlen(c_s)+1 lugar de strlen(c_s) , por lo que s es "Hello World!/0" , no solo "Hello World!" .
( Por supuesto, en este caso particular funciona incluso con strlen(c_s) . Pero con un fresh & str no se puede garantizar que la cadena C resultante termine donde se espera ) .
Este es el resultado de ejecutar el código:

s == "Hello World!/u{0}" Hello World!


La mejor manera de trabajar con cadenas C en Rust es usar estructuras del módulo std::ffi , concretamente CStr y CString .

CStr es un tipo de tamaño dinámico y, por lo tanto, solo se puede utilizar a través de un puntero. Esto lo hace muy similar al tipo str normal. Puede construir un &CStr desde *const c_char usando un CStr::from_ptr inseguro CStr::from_ptr static. Este método no es seguro porque no hay garantía de que el puntero sin procesar que le pasa sea válido, que realmente apunte a una cadena C válida y que la duración de la cadena sea la correcta.

Puedes obtener un &str de un &CStr usando su método to_str() .

Aquí hay un ejemplo:

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 tener en cuenta la duración de sus punteros *const c_char y quién los posee. Dependiendo de la API C, es posible que necesite llamar a una función especial de desasignación en la cadena. Debe organizar cuidadosamente las conversiones para que las rebanadas no sobrevivan al puntero. El hecho de que CStr::from_ptr devuelva un &CStr con duración arbitraria ayuda aquí (aunque es peligroso por sí mismo); por ejemplo, puede encapsular su cadena C en una estructura y proporcionar una conversión Deref para que pueda usar su estructura como si fuera un segmento de cadena:

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() } }

También hay otro tipo en este módulo llamado CString . Tiene la misma relación con CStr que String con str - CString es una versión de propiedad de CStr . Esto significa que "retiene" el control de la asignación de los datos de bytes, y al eliminar CString liberaría la memoria que proporciona (esencialmente, CString ajusta a Vec<u8> , y este último se descartará). En consecuencia, es útil cuando desea exponer los datos asignados en Rust como una cadena C.

Desafortunadamente, las cadenas C siempre terminan con el byte cero y no pueden contener una dentro de ellas, mientras que Rust &[u8] / Vec<u8> son exactamente lo opuesto: no terminan en byte cero y pueden contener números arbitrarios de ellos. dentro. Esto significa que pasar de Vec<u8> a CString no está libre de errores ni de asignación: el constructor de CString comprueba los ceros dentro de los datos proporcionados, devuelve un error si encuentra alguno y agrega un byte cero al final del vector de bytes que puede requerir su reasignación.

Al igual que String , que implementa Deref<Target = str> , CString implementa Deref<Target = CStr> , por lo que puede llamar a los métodos definidos en CStr directamente en CString . Esto es importante porque el método as_ptr() que devuelve *const c_char necesario para la interoperación C se define en CStr . Puede llamar a este método directamente en los valores de CString , lo cual es conveniente.

CString se puede crear a partir de todo lo que se puede convertir a Vec<u8> . String , &str , Vec<u8> y &[u8] son argumentos válidos para la función de constructor, CString::new() . Naturalmente, si pasa un segmento de bytes o un segmento de cadena, se creará una nueva asignación, mientras que Vec<u8> o String se consumirán.

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), } }

Si necesita transferir la propiedad del código CString a C, puede llamar a CString::into_raw . A continuación, se requiere que recuperes el puntero y lo liberes en Rust; es poco probable que el asignador de óxido sea el mismo que el asignador utilizado por malloc y free . Todo lo que necesita hacer es llamar a CString::from_raw y luego permitir que la cadena se CString::from_raw normalmente.