簡體   English   中英

如何將 C 字符串轉換為 Rust 字符串並通過 FFI 返回?

[英]How do I convert a C string into a Rust string and back via FFI?

我正在嘗試獲取 C 庫返回的 C 字符串,並通過 FFI 將其轉換為 Rust 字符串。

mylib.c

const char* hello(){
    return "Hello World!";
}

主文件

#![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?
}

在 Rust 中使用 C 字符串的最佳方法是使用來自std::ffi模塊的結構,即CStrCString

CStr是一種動態大小的類型,因此它只能通過指針使用。 這使它與常規str類型非常相似。 您可以使用不安全的CStr::from_ptr靜態方法從*const c_char構造&CStr 這個方法是不安全的,因為不能保證傳遞給它的原始指針是有效的,它確實指向一個有效的 C 字符串,並且該字符串的生命周期是正確的。

您可以使用其to_str()方法從&CStr獲取&str

下面是一個例子:

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指針的生命周期以及擁有它們的人。 根據 C API,您可能需要對字符串調用特殊的釋放函數。 您需要仔細安排轉換,以便切片不會超過指針。 CStr::from_ptr返回具有任意生命周期的&CStr這一事實在這里有所幫助(盡管它本身很危險); 例如,您可以將 C 字符串封裝到結構中並提供Deref轉換,這樣您就可以像使用字符串切片一樣使用結構:

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

此模塊中還有另一種類型,稱為CString 它與CStr關系與Stringstr關系相同 - CStringCStr的擁有版本。 這意味着它“持有”字節數據分配的句柄,刪除CString將釋放它提供的內存(本質上, CString包裝了Vec<u8> ,后者將被刪除)。 因此,當您想將 Rust 中分配的數據公開為 C 字符串時,它很有用。

不幸的是,C 字符串總是以零字節結尾,並且其中不能包含 1,而 Rust &[u8] / Vec<u8>恰恰相反——它們不以零字節結尾,可以包含任意數量的字符串里面。 這意味着從Vec<u8>CString既不是無錯誤也不是無分配 - CString構造函數都會檢查您提供的數據中的零,如果找到一些則返回錯誤,並在末尾附加一個零字節可能需要重新分配的字節向量。

與實現Deref<Target = str> String一樣, CString實現Deref<Target = CStr> ,因此您可以直接在CString上調用在CStr上定義的方法。 這很重要,因為返回 C 互操作所需的*const c_charas_ptr()方法是在CStr上定義的。 可以直接在CString值上調用這個方法,很方便。

CString可以從所有可以轉換為Vec<u8>東西中創建。 String&strVec<u8>&[u8]是構造函數CString::new()有效參數。 自然地,如果您傳遞一個字節切片或字符串切片,則會創建一個新的分配,而Vec<u8>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),
    }  
}

如果您需要將CString所有權轉移到 C 代碼,您可以調用CString::into_raw 然后你需要取回指針並在 Rust 中釋放它; Rust 分配器不太可能與mallocfree使用的分配器相同。 您需要做的就是調用CString::from_raw然后允許字符串正常刪除。

除了@vladimir-matveev 所說的之外,您還可以在沒有CStrCString幫助的情況下在它們之間進行轉換:

#![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);
    }
}

只需確保從 &str 轉換為 C 字符串時,您的 &str 以'\\0'結尾。 請注意,在上面的代碼中,我使用strlen(c_s)+1而不是strlen(c_s) ,所以s"Hello World!\\0" ,而不僅僅是"Hello World!" .
當然,在這種特殊情況下,它甚至可以與strlen(c_s) 。但是使用新的 &str 您不能保證生成的 C 字符串會在預期的地方終止。
下面是運行代碼的結果:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM