Ich versuche, einen C-String von einer C-Bibliothek zurückzugeben und ihn über FFI in einen Rust-String zu konvertieren.
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?
}
Der beste Weg, um mit C-Saiten in Rust zu arbeiten, ist die Verwendung von Strukturen aus der std::ffi
Modul, nämlich CStr
und CString
.
CStr
ist ein Typ mit dynamischer Größe und kann daher nur über einen Zeiger verwendet werden. Dadurch ist es dem regulären sehr ähnlich str
Typ. Sie können eine konstruieren &CStr
aus *const c_char
mit einem unsicheren CStr::from_ptr
statische Methode. Diese Methode ist unsicher, da es keine Garantie dafür gibt, dass der rohe Zeiger, den Sie übergeben, gültig ist, dass er wirklich auf einen gültigen C-String zeigt und dass die Lebensdauer des Strings korrekt ist.
Sie können eine bekommen &str
von einem &CStr
mit seiner to_str()
Methode.
Hier ist ein Beispiel:
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
}
Sie müssen die Lebensdauer Ihrer berücksichtigen *const c_char
Zeiger und wem sie gehören. Abhängig von der C-API müssen Sie möglicherweise eine spezielle Freigabefunktion für die Zeichenfolge aufrufen. Sie müssen Konvertierungen sorgfältig arrangieren, damit die Slices den Zeiger nicht überleben. Die Tatsache, dass CStr::from_ptr
gibt a zurück &CStr
mit beliebiger Lebensdauer hilft hier (obwohl es an sich gefährlich ist); Beispielsweise können Sie Ihren C-String in eine Struktur kapseln und eine bereitstellen Deref
Konvertierung, damit Sie Ihre Struktur verwenden können, als wäre es ein String-Slice:
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()
}
}
Es gibt auch einen anderen Typ in diesem Modul namens CString
. Es hat die gleiche Beziehung zu CStr
wie String
mit str
– CString
ist eine eigene Version von CStr
. Dies bedeutet, dass es das Handle zur Zuweisung der Bytedaten “hält” und verwirft CString
würde den bereitgestellten Speicher freigeben (im Wesentlichen CString
wickelt Vec<u8>
, und letzteres wird fallen gelassen). Folglich ist es nützlich, wenn Sie die in Rust zugewiesenen Daten als C-String verfügbar machen möchten.
Leider enden C-Strings immer mit dem Null-Byte und können keins enthalten, während Rust &[u8]
/Vec<u8>
sind genau das Gegenteil – sie enden nicht mit null Byte und können eine beliebige Anzahl davon enthalten. Das bedeutet, dass ab Vec<u8>
zu CString
ist weder fehlerfrei noch allokationsfrei – die CString
Der Konstruktor sucht in den von Ihnen bereitgestellten Daten nach Nullen, gibt einen Fehler zurück, wenn er welche findet, und fügt ein Null-Byte an das Ende des Byte-Vektors an, das möglicherweise neu zugewiesen werden muss.
Wie String
die implementiert Deref<Target = str>
, CString
implementiert Deref<Target = CStr>
sodass Sie definierte Methoden aufrufen können CStr
direkt an CString
. Dies ist wichtig, weil die as_ptr()
Methode, die die zurückgibt *const c_char
die für die C-Interoperation erforderlich ist, wird definiert CStr
. Sie können diese Methode direkt aufrufen CString
Werte, was praktisch ist.
CString
kann aus allem erstellt werden, was konvertiert werden kann Vec<u8>
. String
, &str
, Vec<u8>
und &[u8]
sind gültige Argumente für die Konstruktorfunktion, CString::new()
. Wenn Sie einen Byte-Slice oder einen String-Slice übergeben, wird natürlich eine neue Zuordnung erstellt, während Vec<u8>
oder String
wird verbraucht.
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),
}
}
Wenn Sie das Eigentum an der übertragen müssen CString
zu C-Code können Sie anrufen CString::into_raw
. Sie müssen dann den Zeiger zurückholen und in Rust freigeben; Es ist unwahrscheinlich, dass der Rust-Zuordner mit dem von verwendeten Zuordner identisch ist malloc
und free
. Alles, was Sie tun müssen, ist anzurufen CString::from_raw
und lassen Sie dann die Zeichenfolge normal fallen.
Zusätzlich zu dem, was @vladimir-matveev gesagt hat, können Sie auch ohne die Hilfe von zwischen ihnen konvertieren CStr
oder 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);
}
}
Stellen Sie nur sicher, dass bei der Konvertierung von einem &str in einen C-String Ihr &str mit endet '\0'
. Beachten Sie, dass ich im obigen Code verwende strlen(c_s)+1
Anstatt von strlen(c_s)
Also s
ist "Hello World!\0"
nicht nur "Hello World!"
.
(Natürlich funktioniert es in diesem speziellen Fall auch nur mit strlen(c_s)
. Aber mit einem frischen &str konnten Sie nicht garantieren, dass der resultierende C-String wie erwartet enden würde.)
Hier ist das Ergebnis der Ausführung des Codes:
s == "Hello World!\u{0}"
Hello World!