如何将 C 字符串转换为 Rust 字符串并通过 FFI 返回? [英] How do I convert a C string into a Rust string and back via FFI?

查看:47
本文介绍了如何将 C 字符串转换为 Rust 字符串并通过 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!";
}

ma​​in.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 模块,即 CStrCString.

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 的关系与 Stringstr 的关系相同 - CStringCStr 的拥有版本.这意味着它持有"字节数据分配的句柄,并且删除 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 恰好是相反的事情 - 它们不以零字节结尾,并且可以在其中包含任意数量的它们.这意味着从 VecCString 既不是无错误也不是无分配的 - 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,实现了DerefCString实现了Deref,所以你可以直接在CString上调用CStr上定义的方法.这很重要,因为 as_ptr()*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&strVec&[u8] 是构造函数的有效参数函数,CString::new().自然,如果你传递一个字节切片或字符串切片,将创建一个新的分配,而 VecString 将被消耗.

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 分配器不太可能与 mallocfree 使用的分配器相同.您需要做的就是调用 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆