[英]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
模塊的結構,即CStr
和CString
。
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
關系與String
與str
關系相同 - CString
是CStr
的擁有版本。 這意味着它“持有”字節數據分配的句柄,刪除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_char
的as_ptr()
方法是在CStr
上定義的。 可以直接在CString
值上調用這個方法,很方便。
CString
可以從所有可以轉換為Vec<u8>
東西中創建。 String
、 &str
、 Vec<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 分配器不太可能與malloc
和free
使用的分配器相同。 您需要做的就是調用CString::from_raw
然后允許字符串正常刪除。
除了@vladimir-matveev 所說的之外,您還可以在沒有CStr
或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);
}
}
只需確保從 &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.