如何从Rust正确地填充C字符串指针?

6

我有一个需要实现的FFI签名:

pub unsafe extern fn f(header_size: u32, header_ptr: *mut u8) -> i32;

一个FFI调用者需要提供一个缓冲区header_ptr和该缓冲区的大小header_size。Rust需要将一个字符串填充到该缓冲区中,直到达到header_size的大小,并在成功时返回0。FFI调用者需要将该字符串解释为ASCII。
如果我有一个包含要提供内容的headers:&str,最符合惯用法的方式是什么?
目前我的做法是:
let header_bytes = slice::from_raw_parts_mut(header_ptr, header_size as usize);

if header_bytes.len() < headers.len() { return Errors::IndexOutOfBounds as i32; }

for (i, byte) in headers.as_bytes().iter().enumerate() {
    header_bytes[i] = *byte;
}

但这感觉不对。

编辑,我认为这不是这个问题的完全重复,因为我的问题涉及字符串,而我记得在将&str转换为CStrings时有特殊考虑。


1
看起来它完成了工作。为什么感觉不对? - MB-F
@kazemakase,我更新了我的问题,因为将“&str”转换为“C”字符串可能会出现问题。 - left4bread
1个回答

10
由于C字符串只不过是以0结尾的字节数组,因此将Rust字符串转换为C字符串非常简单。几乎每个有效的Rust字符串也都是有效的C字符串,但您必须确保C字符串以0字符结尾,并且字符串中没有其他任何0字符。
Rust提供了一个类型来处理转换:CString
如果您的输入字符串已成功转换为CString,则可以简单地复制字节而不必担心细节。
use std::slice;
use std::ffi::CString;

pub unsafe extern fn f(header_size: u32, header_ptr: *mut u8) -> i32 {
    let headers = "abc";
    let c_headers = match CString::new(headers) {
        Ok(cs) => cs,
        Err(_) => return -1,  // failed to convert to C string
    };
    let bytes = c_headers.as_bytes_with_nul();

    let header_bytes = slice::from_raw_parts_mut(header_ptr, header_size as usize);
    header_bytes[..bytes.len()].copy_from_slice(bytes);

    0  // success
}

fn main() {
    let mut h = [1u8; 8];

    unsafe {
        f(h.len() as u32, h.as_mut_ptr());
    }

    println!("{:?}", h);  // [97, 98, 99, 0, 1, 1, 1, 1]
}

值得注意的是,为了简洁起见,我省略了长度检查。如果缓冲区太短,header_bytes[..bytes.len()] 会引发 panic。如果从 C 中调用 f,这是您要避免的。


感谢您展示了在此情境下应如何使用 match CString::new - left4bread

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接