Rust str和ffi :: CString之间的转换再次返回部分会破坏字符串

时间:2018-01-12 23:11:26

标签: rust

#![allow(non_camel_case_types)]

use libc::{c_uchar, size_t};
use std::str::FromStr;
use std::ffi::{CString, NulError};
use std::slice;

#[repr(C)]
pub struct c_str_t {
    pub len: size_t,
    pub data: *const c_uchar,
}

pub trait MyCStrExt<T> {
    fn to_c_str(&self) -> Result<c_str_t, NulError>;
}


pub trait MyCStringExt {
    fn from_c_str_ref(nstr: &c_str_t) -> Option<String>;
}

impl<'a> MyCStrExt<&'a str> for str {
    fn to_c_str(&self) -> Result<c_str_t, NulError> {
        let result = match CString::new(&self[..]) {
            Ok(result) => result,
            Err(e) => {
                return Err(e);
            }
        };
        Ok(c_str_t { data: result.as_ptr() as *const u8, len: self.len() })
    }
}


impl MyCStringExt for String {
    fn from_c_str_ref(nstr: &c_str_t) -> Option<String> {
        unsafe {
            if nstr.data.is_null() {
                return None;
            }
            let value = slice::from_raw_parts(nstr.data, nstr.len);
            match String::from_utf8(value.to_vec()) {
                Ok(value) => Some(value),
                Err(e) => None
            }
        }
    }
}

使用此测试首先转换为CString,然后再返回到Rust字符串,传入给定的字符串

#[test]
fn test_to_c_str() {
    let s = "What does the fox say?";
    let result = s.to_c_str();
    let round_trip = String::from_c_str_ref(result.as_ref().ok().unwrap());
    println!("{:?}", round_trip);
}

将导致最后一个带有Rust字符串的往返,在第一个字符位置为null:

Some("\u{0}hat does the fox say?")

我做错了什么?

1 个答案:

答案 0 :(得分:5)

你错过了一个关键步骤。

fn to_c_str(&self) -> Result<c_str_t, NulError> {
    let result = match CString::new(&self[..]) {
        Ok(result) => result,
        Err(e) => {
            return Err(e);
        }
    };
    Ok(c_str_t { data: result.as_ptr() as *const u8, len: self.len() })
}

分配一个新的CString结构,并获取指向其数据的指针,但是一旦to_c_str函数运行完成,该数据仍将被释放。这意味着以后的代码可以覆盖内存中的字符串内容。在您的示例中,恰好只会覆盖第一个字符。

我建议阅读.as_ptr()的文档,因为它试图涵盖其中一些内容。

您可以手动std::mem::forget,例如

fn to_c_str(&self) -> Result<c_str_t, NulError> {
    let result = match CString::new(&self[..]) {
        Ok(result) => result,
        Err(e) => {
            return Err(e);
        }
    };
    let s = c_str_t { data: result.as_ptr() as *const u8, len: self.len() };
    std::mem::forget(result);

    Ok(s)
}

但最好的方法是使用.into_raw()取得所有权并自行返回指针。

fn to_c_str(&self) -> Result<c_str_t, NulError> {
    let result = match CString::new(&self[..]) {
        Ok(result) => result,
        Err(e) => {
            return Err(e);
        }
    };
    Ok(c_str_t { data: result.into_raw() as *const u8, len: self.len() })
}