我正在尝试获取C库返回的C字符串,并通过FFI将其转换为Rust字符串。
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?
}
答案 0 :(得分:72)
在Rust中使用C字符串的最佳方法是使用std::ffi
模块中的结构,即CStr
和CString
。
CStr
是动态大小的类型,因此只能通过指针使用。这使它与常规str
类型非常相似。您可以使用不安全的CStr::from_ptr
静态方法从&CStr
构建*const c_char
。此方法不安全,因为无法保证传递给它的原始指针有效,它确实指向有效的C字符串并且字符串的生存期是正确的。
您可以使用to_str()
方法从&str
获得&CStr
。
以下是一个例子:
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
是[{1}}的拥有版本。这意味着它“保持”字节数据分配的句柄,而删除CStr
将释放它提供的内存(基本上,CString
包裹CString
,而后者则是将被删除)。因此,当您希望将Rust中分配的数据公开为C字符串时,它非常有用。
不幸的是,C字符串总是以零字节结尾,并且不能在其中包含一个字符串,而Rust Vec<u8>
/ &[u8]
正好相反 - 它们不以零字节结束并且可以里面包含任意数量的它们。这意味着从Vec<u8>
到Vec<u8>
既不是无错误也不是无分配 - CString
构造函数都检查您提供的数据中的零,如果发现某些内容则返回错误,并在字节向量的末尾附加一个零字节,这可能需要重新分配。
与实施CString
的{{1}}一样,String
实施Deref<Target = str>
,因此您可以直接在CString
上调用Deref<Target = CStr>
上定义的方法。这很重要,因为在CStr
上定义了返回C互操作所需的CString
的{{3}}方法。您可以直接在*const c_char
值上调用此方法,这很方便。
CStr
的所有内容创建 CString
。 CString
,Vec<u8>
,String
和&str
是构造函数as_ptr()
的有效参数。当然,如果传递字节切片或字符串切片,将创建新的分配,而Vec<u8>
或&[u8]
将被消耗。
Vec<u8>
如果您需要将String
的所有权转移到C代码,则可以致电CString::new()
。然后,您需要将指针返回并在Rust中释放它; Rust分配器不太可能与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
使用的分配器相同。您需要做的就是调用CString::into_raw
,然后允许正常删除字符串。
答案 1 :(得分:-3)
除了@ 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);
}
}
只需确保从&amp; str转换为C字符串时,&amp; str以'\0'
结尾。
请注意,在上面的代码中,我使用的是strlen(c_s)+1
而不是strlen(c_s)
,因此s
为"Hello World!\0"
,而不仅仅是"Hello World!"
。
(当然在这种特殊情况下,即使只有strlen(c_s)
它也能正常工作。但是对于一个新的&amp; str,你不能保证得到的C字符串会在预期的地方终止。)
这是运行代码的结果:
s == "Hello World!\u{0}"
Hello World!