J'essaie d'obtenir une chaîne C retournée par une bibliothèque C et de la convertir en une chaîne Rust via 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?
}
La meilleure façon de travailler avec des chaînes C dans Rust est d'utiliser les structures du module std::ffi
, à savoir CStr
et CString
.
CStr
est un type de taille dynamique et ne peut donc être utilisé que via un pointeur. Cela le rend très similaire au type normal str
. Vous pouvez construire un &CStr
À partir de *const c_char
En utilisant une méthode statique non sûre CStr::from_ptr
. Cette méthode n'est pas sûre car il n'y a aucune garantie que le pointeur brut que vous lui passez est valide, qu'il pointe vraiment vers une chaîne C valide et que la durée de vie de la chaîne est correcte.
Vous pouvez obtenir un &str
À partir d'un &CStr
En utilisant sa méthode to_str()
.
Voici un exemple:
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
}
Vous devez prendre en compte la durée de vie de vos pointeurs *const c_char
Et leur propriétaire. Selon l'API C, vous devrez peut-être appeler une fonction de désallocation spéciale sur la chaîne. Vous devez organiser soigneusement les conversions afin que les tranches ne survivent pas au pointeur. Le fait que CStr::from_ptr
Renvoie un &CStr
Avec une durée de vie arbitraire aide ici (bien qu'il soit dangereux en soi); par exemple, vous pouvez encapsuler votre chaîne C dans une structure et fournir une conversion Deref
afin que vous puissiez utiliser votre structure comme s'il s'agissait d'une tranche de chaîne:
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()
}
}
Il existe également un autre type dans ce module appelé CString
. Il a la même relation avec CStr
que String
avec str
- CString
est une version appartenant à CStr
. Cela signifie qu'il "détient" le descripteur de l'allocation des données d'octets, et la suppression de CString
libérerait la mémoire qu'il fournit (essentiellement, CString
encapsule Vec<u8>
, Et c'est ce dernier qui sera abandonné). Par conséquent, il est utile lorsque vous souhaitez exposer les données allouées dans Rust sous forme de chaîne C.
Malheureusement, les chaînes C se terminent toujours par l'octet zéro et ne peuvent pas en contenir un à l'intérieur, tandis que Rust &[u8]
/Vec<u8>
Sont exactement l'inverse - elles ne se terminent pas par zéro octet et peuvent en contenir des nombres arbitraires. Cela signifie que passer de Vec<u8>
à CString
n'est ni sans erreur ni allocation - le CString
Le constructeur vérifie les zéros à l'intérieur des données que vous fournissez, renvoyant une erreur s'il en trouve, et ajoute un octet zéro à la fin du vecteur d'octets qui peut nécessiter sa réallocation.
Comme String
, qui implémente Deref<Target = str>
, CString
implémente Deref<Target = CStr>
, Vous pouvez donc appeler des méthodes définies sur CStr
directement sur CString
. Ceci est important car la méthode as_ptr()
qui renvoie le *const c_char
Nécessaire à l'interopérabilité C est définie sur CStr
. Vous pouvez appeler cette méthode directement sur les valeurs de CString
, ce qui est pratique.
CString
peut être créé à partir de tout ce qui peut être converti en Vec<u8>
. String
, &str
, Vec<u8>
Et &[u8]
Sont des arguments valides pour la fonction constructeur, CString::new()
. Naturellement, si vous passez une tranche d'octets ou une tranche de chaîne, une nouvelle allocation sera créée, tandis que Vec<u8>
Ou String
seront consommés.
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 vous devez transférer la propriété du code CString
au code C, vous pouvez appeler CString::into_raw
. Vous devez ensuite récupérer le pointeur et le libérer dans Rust; l'allocateur Rust ne sera probablement pas le même que l'allocateur utilisé par malloc
et free
. Il vous suffit d'appeler CString::from_raw
puis laissez la chaîne être supprimée normalement.