2014-06-10 13 views
27

Próbuję uzyskać ciąg znaków C zwrócony przez bibliotekę C i przekonwertować go na ciąg Rust przez FFI.Jak przekonwertować łańcuch C na łańcuch Rust i wrócić przez 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? 
} 

Odpowiedz

58

Najlepszym sposobem, aby pracować z ciągów C w Rust jest stosowanie struktur z modułu std::ffi, mianowicie CStr i CString.

CStr to typ o dynamicznym rozmiarze, więc można go używać tylko za pomocą wskaźnika. To sprawia, że ​​jest bardzo podobny do zwykłego typu str. Możesz skonstruować &CStr z *const c_char używając niebezpiecznej metody statycznej CStr::from_ptr. Ta metoda jest niebezpieczna, ponieważ nie ma gwarancji, że surowy wskaźnik, który do niej trafisz, jest poprawny, że rzeczywiście wskazuje poprawny łańcuch C i że jego żywotność jest poprawna.

Można uzyskać &str z &CStr przy użyciu metody to_str().

Oto przykład:

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 
} 

Trzeba wziąć pod uwagę cały okres użytkowania *const c_char wskaźników i kto posiada je. W zależności od API C, może zajść potrzeba wywołania specjalnej funkcji dealowania na łańcuchu. Musisz dokładnie ułożyć konwersje, aby plasterki nie przeżyły wskaźnika. Fakt, że CStr::from_ptr zwraca wartość &CStr z dowolnym czasem życia, pomaga (ale sama w sobie jest niebezpieczna); Na przykład, można upakować Twojego C ciąg do struktury i zapewnienie Deref konwersji, dzięki czemu można używać swój struct jakby to był kawałek ciąg:

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

Istnieje również inny typ w tym module o nazwie CString. Ma on tę samą relację z CStr jako String z str - CString jest własnością użytkownika CStr. Oznacza to, że "przechowuje" uchwyt do alokacji danych bajtowych, a porzucenie CString uwolniłoby pamięć, którą zapewnia (zasadniczo CString zawija Vec<u8>, i to ta ostatnia, która zostanie usunięta). W związku z tym jest to przydatne, gdy chcemy ujawnić dane przydzielone w Rust jako ciąg C.

Niestety, smyczki C zawsze kończy się bajtem zerowym i nie może zawierać jeden wewnątrz nich, a Rust &[u8]/Vec<u8> są dokładnie odwrotnie rzecz - nie kończy się bajtem zerowym i może zawierać dowolne numery z nich w środku. Oznacza to, że przechodzenie z wersji Vec<u8> do CString nie jest wolne od błędów ani alokacji - konstruktor CString sprawdza zarówno zera w podanych danych, zwracając błąd, jeśli znajdzie jakieś, i dołącza zero bajtów do końca bajtu wektor, który może wymagać ponownego przydziału.

Jak String, który realizuje Deref<Target = str>, CString realizuje Deref<Target = CStr>, więc można wywołać metody zdefiniowane na CStr bezpośrednio na CString.Jest to ważne, ponieważ metoda as_ptr(), która zwraca wartość *const c_char niezbędną do współdziałania C, jest zdefiniowana na CStr. Możesz wywołać tę metodę bezpośrednio na wartościach CString, co jest wygodne.

CString można utworzyć ze wszystkiego, co można przekonwertować na Vec<u8>. String, &str, Vec<u8> i &[u8] są poprawnymi argumentami dla funkcji konstruktora, CString::new(). Naturalnie, jeśli przekażesz bajtowy wycinek lub wycinek łańcucha, zostanie utworzona nowa alokacja, a zostanie zużyta Vec<u8> lub 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), 
    } 
} 

Jeśli trzeba przenieść własność CString do kodu C, można zadzwonić CString.html::into_raw. Następnie musisz odzyskać wskaźnik i uwolnić go w Rust; Przydzielanie Rust jest mało prawdopodobne, aby było to to samo, co program przydzielający używany przez malloc i free. Wszystko, co musisz zrobić, to zadzwonić pod numer CString::from_raw, a następnie zezwolić na normalne upuszczenie łańcucha.

+0

Świetna odpowiedź, to mi pomogło. Czy nie ma bezpieczeństwa w czasie życia cstr, gdy istnieje połączenie z językiem GC, jak C#? – scape

+0

@scape tak, oczywiście, to robi. Powiedziałbym, że jest to jeszcze ważniejsze, ponieważ odśmiecanie może zostać uruchomione w dowolnym momencie, zwłaszcza jeśli jest współbieżne. Jeśli nie dbasz o to, aby sznur po stronie GC został ukorzeniony gdzieś, możesz nagle uzyskać dostęp do uwolnionego fragmentu pamięci po stronie Rdzy. –

-3

Oprócz tego, co @ Vladimir-Matveev powiedział, można również konwertować między nimi bez pomocy CStr lub 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); 
    } 
} 

Wystarczy upewnić się, że podczas konwersji z & ul do łańcucha C , twoja & str kończy się na '\0'. Należy zauważyć, że w powyższym kodzie używam strlen(c_s)+1 zamiast strlen(c_s), więc s jest "Hello World!\0", a nie tylko "Hello World!".
(. oczywiście w tym konkretnym przypadku to działa nawet z tylko strlen(c_s) Ale ze świeżym & ul Państwo nie może zagwarantować, że otrzymany C ciąg byłoby zakończyć gdzie oczekuje.)
Oto wynik działania kodu:

s == "Hello World!\u{0}" 
Hello World! 
+0

Możesz przekonwertować * z * bez 'CStr', ale unikanie go nie ma żadnego powodu. Twoja konwersja z powrotem jest * niepoprawna *, ponieważ Rust '& str' nie jest zakończony NUL, więc nie jest poprawnym łańcuchem C. – Shepmaster

+0

@Shepmaster, Tak, a Rust i str nie są na ogół zakończone NUL, ale ponieważ ten został napisany z łańcucha C, działa dobrze, gdy wykonujesz 's.as_ptr()'. Aby było to bardziej zrozumiałe, poprawiłem 'strlen (c_s)' na 'strlen (c_s) + 1'. –

+0

Więc teraz powielono funkcjonalność ze standardowej biblioteki? Proszę [edytuj] swoje pytanie, aby wyjaśnić przyszłym czytelnikom, dlaczego powinni wybrać to rozwiązanie w przeciwieństwie do istniejącej odpowiedzi. – Shepmaster

Powiązane problemy