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

查看:96
本文介绍了如何将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!";
}

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字符串的最佳方法是使用 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类型非常相似.您可以使用不安全的*const c_char构造&CStr > 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.

您可以使用&CStr 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字符串总是以零字节结尾,并且不能在其中包含一个,而Rust &[u8]/Vec<u8>恰恰相反-它们不以零字节结尾并且可以包含任意数量的他们里面.这意味着从Vec<u8>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.

类似于实现Deref<Target = str>StringCString实现Deref<Target = CStr>,因此您可以直接在CString上调用在CStr上定义的方法.这很重要,因为 as_ptr() 方法在CStr上定义了返回C互操作所必需的*const c_char的函数.您可以直接在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<u8>的所有内容创建. String&strVec<u8>&[u8]是构造函数

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::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天全站免登陆