¿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.