Rust : 如何将C字符串转换为Rust字符串并通过FFI返回?

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

我正在尝试获取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字符串的最佳方法是使用CString::from_raw模块中的结构,即CString::from_raw和CString::from_raw。

CString::from_raw是动态大小的类型,因此只能通过指针使用。 这使其与常规malloc类型非常相似。 您可以使用不安全的CString::from_raw静态方法从&str构造free。 此方法是不安全的,因为不能保证传递给它的原始指针有效,它确实指向有效的C字符串,并且该字符串的生存期是正确的。

您可以使用malloc的CString::from_raw方法获得CString::from_raw。

这是一个例子:

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
}

您需要考虑CString::from_raw指针的寿命以及谁拥有它们。 根据C API,您可能需要在字符串上调用特殊的释放函数。 您需要仔细安排转换,以使切片不会超过指针的寿命。 malloc返回具有任意生存期的free的事实在这里有帮助(尽管它本身很危险); 例如,您可以将C字符串封装到一个结构中,并提供&str转换,以便您可以像使用字符串切片一样使用该结构:

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::from_raw。它与CString::from_raw具有malloc,其中free具有相同的关系-&str是Vec< u8 >的拥有版本。这意味着它“持有”字节数据分配的句柄,并丢弃该句柄 &[u8]会释放它提供的内存(实际上,Vec< u8>包装String,而后者将被丢弃)。 因此,当您希望将Rust中分配的数据公开为C字符串时,这很有用。

不幸的是,C字符串总是以零字节结尾,并且不能在其中包含一个,而Rust CString::from_raw/malloc恰好相反-它们不以零字节结尾,并且可以在其中包含任意数量的字符串。 这意味着从free到&str既没有错误也没有分配-Vec< u8 >构造函数都检查您提供的数据内部是否为零,如果发现错误则返回错误,并将零字节附加到字节的末尾 向量可能需要重新分配。

就像实现malloc的CString::from_raw,实现&str的free一样,您可以直接在&[u8]上调用在Vec< u8 >上定义的方法。这很重要,因为CString::from_raw方法返回了2950000612479599599622的调用,而2950000612479599599是直接在C上进行互操作所必需的。 CString值,这很方便。

可以从可以转换为malloc的所有内容中创建CString::from_raw。free、&str、Vec< u8 >和&[u8]是构造函数&[u8]的有效参数。自然地,如果您传递字节片或字符串片,则会创建新的分配, 而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::from_raw的所有权转让给C代码,则可以调用CString::from_raw。然后,您需要取回指针并在Rust中释放它; Rust分配器不太可能与malloc和free使用的分配器相同。您所需要做的只是调用CString::from_raw,然后允许正常删除字符串。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值