我正在try 获取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模块中的 struct ,即CStrCString.

CStr是动态大小的类型,因此只能通过指针使用.这使得它非常类似于常规的str型.您可以使用不安全的CStr::from_ptr型静电方法从*const c_char型构造&CStr型.此方法是不安全的,因为不能保证传递给它的原始指针有效,不能保证它确实指向有效的C字符串,也不能保证字符串的生存期是正确的.

你可以用to_str()法从&CStr中得到&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字符串封装到 struct 中,并提供Deref转换,这样您就可以像使用字符串片段一样使用 struct :

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-CString的关系相同,它是CStr的自有版本.这意味着它"持有"字节数据分配的句柄,删除CString将释放它提供的内存(本质上,CString包装Vec<u8>,而后者将被删除).因此,当您希望将Rust中分配的数据作为C字符串公开时,它非常有用.

不幸的是,C字符串总是以零字节结尾,并且不能在其中包含一个字节,而Rust &[u8]/Vec<u8>正好相反——它们不以零字节结尾,并且可以在其中包含任意数量的字符串.这意味着从Vec<u8>CString既不是无错误的,也不是无分配的——CString构造函数既会判断您提供的数据中的零,如果发现一些,就会返回一个错误,并在字节向量的末尾追加一个零字节,这可能需要重新分配.

String实现了Deref<Target = str>CString实现了Deref<Target = CStr>,所以可以直接在CString上调用CStr上定义的方法.这很重要,因为返回C互操作所需*const c_charas_ptr()方法是在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++相关问答推荐

当main函数调用被重构时,C函数给出错误的结果

两个连续的语句是否按顺序排列?

C:fopen是如何实现二进制模式和文本模式的?

为什么内核使用扩展到前后相同的宏定义?

如何知道我是否从非阻塞套接字读取所有内容

如何在c++中包装返回空*的函数

如何将常量char*复制到char数组

Vcpkg的配置文件

是否可以通过调用两个函数来初始化2D数组?示例:ARRAY[STARTING_ROWS()][STARTING_COLUMNS()]

接受任何参数的函数指针是否与接受不同参数的函数兼容

为什么我的旧式&q;函数在传递浮点数时会打印2?

从文件到链表读取日期

C语言中的指针和多维数组

在C中,为什么这个带有递增整数的main函数从不因溢出而崩溃?

中位数和众数不正确

GnuCobol 使用 double 类型的参数调用 C 函数

GDB 跳过动态加载器代码

多行表达式:C 编译器如何处理换行符?

从 COBOL 调用外部 C 库

C 语言支持 DirectX 9 或 DirectX 11 吗?