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

Dir*_*irk 47 c ffi rust

我正在尝试获取C库返回的C字符串,并通过FFI将其转换为Rust字符串.

mylib.c

const char* hello(){
    return "Hello World!";
}
Run Code Online (Sandbox Code Playgroud)

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?
}
Run Code Online (Sandbox Code Playgroud)

Vla*_*eev 84

在Rust中使用C字符串的最佳方法是使用std::ffi模块中的结构,即CStrCString.

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
}
Run Code Online (Sandbox Code Playgroud)

您需要考虑*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()
    }
}
Run Code Online (Sandbox Code Playgroud)

此模块中还有另一种类型称为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>,所以你可以调用CStr直接定义的方法CString.这很重要,因为定义as_ptr()了返回*const c_charC互操作所必需的方法CStr.您可以直接在CString值上调用此方法,这很方便.

CString可以从可以转换为的所有内容创建Vec<u8>.String,&str,Vec<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),
    }  
}
Run Code Online (Sandbox Code Playgroud)

如果您需要转移CString到C代码的所有权,您可以致电CString::into_raw.然后,您需要将指针返回并在Rust中释放它; 锈分配器不可能是一样由所使用的分配器mallocfree.您需要做的就是调用CString::from_raw然后允许正常删除字符串.


Des*_*ger 7

除了 @vladimir-matveev 所说的之外,您还可以在不借助CStr或 的情况下在它们之间进行转换CString

#![feature(link_args)]

extern crate libc;
use libc::{c_char, puts, strlen};
use std::{slice, str};

#[link_args = "-L . -I . -lmylib"]
extern "C" {
    fn hello() -> *const c_char;
}

fn main() {
    //converting a C string into a Rust string:
    let s = unsafe {
        let c_s = hello();
        str::from_utf8_unchecked(slice::from_raw_parts(c_s as *const u8, strlen(c_s)+1))
    };
    println!("s == {:?}", s);
    //and back:
    unsafe {
        puts(s.as_ptr() as *const c_char);
    }
}
Run Code Online (Sandbox Code Playgroud)

只需确保从 &str 转换为 C 字符串时, &str 以 结尾'\0'。请注意,在上面的代码中,我使用strlen(c_s)+1代替strlen(c_s),所以s"Hello World!\0",而不仅仅是"Hello World!"
当然,在这种特殊情况下,即使只使用 ,它也能工作strlen(c_s)。但使用新的 &str 时,您无法保证生成的 C 字符串会在预期的位置终止。
以下是运行代码的结果:

#![feature(link_args)]

extern crate libc;
use libc::{c_char, puts, strlen};
use std::{slice, str};

#[link_args = "-L . -I . -lmylib"]
extern "C" {
    fn hello() -> *const c_char;
}

fn main() {
    //converting a C string into a Rust string:
    let s = unsafe {
        let c_s = hello();
        str::from_utf8_unchecked(slice::from_raw_parts(c_s as *const u8, strlen(c_s)+1))
    };
    println!("s == {:?}", s);
    //and back:
    unsafe {
        puts(s.as_ptr() as *const c_char);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这样做的原因之一是您正在 no_std 环境中进行开发。 (4认同)