gpt4 book ai didi

c - 如何将 C 字符串转换为 Rust 字符串并通过 FFI 转换回来?

转载 作者:行者123 更新时间:2023-12-03 11:48:16 28 4
gpt4 key购买 nike

我正在尝试获取 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?
}

最佳答案

在 Rust 中使用 C 字符串的最佳方法是使用 std::ffi 模块中的结构,即 CStr CString
CStr 是动态大小的类型,因此只能通过指针使用。这使得它与常规的 str 类型非常相似。您可以使用不安全的 &CStr 静态方法从 *const c_char 构造 CStr::from_ptr。这个方法是不安全的,因为不能保证你传递给它的原始指针是有效的,它确实指向一个有效的 C 字符串并且字符串的生命周期是正确的。

您可以使用 &str 方法从 &CStr 获取 to_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 字符串总是以零字节结尾并且其中不能包含一个,而 Rust &[u8]/ Vec<u8> 正好相反——它们不以零字节结尾并且可以包含任意数量的它们。这意味着从 Vec<u8>CString 既不是无错误也不是无分配 - CString 构造函数都会检查您提供的数据中的零,如果找到则返回错误,并将零字节附加到字节的末尾可能需要重新分配的 vector 。

与实现 StringDeref<Target = str> 一样, CString 实现 Deref<Target = CStr> ,因此您可以直接在 CStr 上调用在 CString 上定义的方法。这很重要,因为返回 C 互操作所需的 as_ptr() *const c_char 方法是在 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 然后让字符串正常删除。

关于c - 如何将 C 字符串转换为 Rust 字符串并通过 FFI 转换回来?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62951377/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com