如何将 C 字符串转换为 Rust 字符串并通过 FFI 返回? [英] How do I convert a C string into a Rust string and back via FFI?
问题描述
我正在尝试获取 C 库返回的 C 字符串,并通过 FFI 将其转换为 Rust 字符串.
I'm trying to get a C string returned by a C library and convert it to a Rust string via 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?
}
推荐答案
在 Rust 中使用 C 字符串的最佳方式是使用来自 std::ffi
模块,即 CStr
和 CString
.
The best way to work with C strings in Rust is to use structures from the std::ffi
module, namely CStr
and CString
.
CStr
是动态大小的类型,因此只能通过指针使用.这使它与常规的 str
类型非常相似.您可以使用不安全的 CStr::from_ptr
静态方法.这个方法是不安全的,因为不能保证你传递给它的原始指针是有效的,它确实指向一个有效的 C 字符串,并且字符串的生命周期是正确的.
CStr
is a dynamically sized type and so it can only be used through a pointer. This makes it very similar to the regular str
type. You can construct a &CStr
from *const c_char
using an unsafe CStr::from_ptr
static method. This method is unsafe because there is no guarantee that the raw pointer you pass to it is valid, that it really does point to a valid C string and that the string's lifetime is correct.
您可以使用 to_str()
方法.
You can get a &str
from a &CStr
using its to_str()
method.
这是一个例子:
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
转换,这样您就可以像使用字符串切片一样使用结构:
You need to take into account the lifetime of your *const c_char
pointers and who owns them. Depending on the C API, you may need to call a special deallocation function on the string. You need to carefully arrange conversions so the slices won't outlive the pointer. The fact that CStr::from_ptr
returns a &CStr
with arbitrary lifetime helps here (though it is dangerous by itself); for example, you can encapsulate your C string into a structure and provide a Deref
conversion so you can use your struct as if it was a 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()
}
}
这个模块中还有另一种类型,叫做 CString代码>
.它与 CStr
的关系与 String
与 str
的关系相同 - CString
是 CStr 的拥有版本代码>.这意味着它持有"字节数据分配的句柄,并且删除
CString
将释放它提供的内存(本质上,CString
包装 Vec<;u8>
,而后者将被丢弃).因此,当您想将 Rust 中分配的数据公开为 C 字符串时,它很有用.
There is also another type in this module called CString
. It has the same relationship with CStr
as String
with str
- CString
is an owned version of CStr
. This means that it "holds" the handle to the allocation of the byte data, and dropping CString
would free the memory it provides (essentially, CString
wraps Vec<u8>
, and it's the latter that will be dropped). Consequently, it is useful when you want to expose the data allocated in Rust as a C string.
不幸的是,C 字符串总是以 0 字节结尾,其中不能包含 1,而 Rust &[u8]
/Vec
恰好是相反的事情 - 它们不以零字节结尾,并且可以在其中包含任意数量的它们.这意味着从 Vec
到 CString
既不是无错误也不是无分配的 - CString
构造函数都检查内部的零您提供的数据,如果找到一些则返回错误,并将零字节附加到可能需要重新分配的字节向量的末尾.
Unfortunately, C strings always end with the zero byte and can't contain one inside them, while Rust &[u8]
/Vec<u8>
are exactly the opposite thing - they do not end with zero byte and can contain arbitrary numbers of them inside. This means that going from Vec<u8>
to CString
is neither error-free nor allocation-free - the CString
constructor both checks for zeros inside the data you provide, returning an error if it finds some, and appends a zero byte to the end of the byte vector which may require its reallocation.
像String
,实现了Deref
,CString
实现了Deref
,所以你可以直接在CString
上调用CStr
上定义的方法.这很重要,因为 as_ptr()返回 C 互操作所需的
方法定义在 *const c_char
的 code>CStr
上.可以直接在CString
值上调用这个方法,很方便.
Like String
, which implements Deref<Target = str>
, CString
implements Deref<Target = CStr>
, so you can call methods defined on CStr
directly on CString
. This is important because the as_ptr()
method that returns the *const c_char
necessary for C interoperation is defined on CStr
. You can call this method directly on CString
values, which is convenient.
CString
可以从所有可以转换为 Vec
的东西中创建.String
、&str
、Vec
和 &[u8]
是构造函数的有效参数函数,CString::new()代码>
.自然,如果你传递一个字节切片或字符串切片,将创建一个新的分配,而 Vec
或 String
将被消耗.
CString
can be created from everything which can be converted to Vec<u8>
. String
, &str
, Vec<u8>
and &[u8]
are valid arguments for the constructor function, CString::new()
. Naturally, if you pass a byte slice or a string slice, a new allocation will be created, while Vec<u8>
or String
will be consumed.
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
然后允许字符串正常删除.
If you need to transfer ownership of the CString
to C code, you can call CString::into_raw
. You are then required to get the pointer back and free it in Rust; the Rust allocator is unlikely to be the same as the allocator used by malloc
and free
. All you need to do is call CString::from_raw
and then allow the string to be dropped normally.
这篇关于如何将 C 字符串转换为 Rust 字符串并通过 FFI 返回?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!