从Rust填充C字符串指针的正确方法是什么?

lef*_*ead 4 c-strings ffi rust

我需要执行一个FFI签名:

pub unsafe extern fn f(header_size: u32, header_ptr: *mut u8) -> i32;
Run Code Online (Sandbox Code Playgroud)

FFI调用程序应提供一个缓冲区header_ptr以及该缓冲区的大小header_size。预期Rust会将一个字符串填充到该缓冲区中,直到header_size0如果成功,则返回。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;
}
Run Code Online (Sandbox Code Playgroud)

但这感觉不对。

编辑,我认为这是不完全相同的副本来这个,因为我的问题涉及到字符串,STR转换和对CStrings时IIRC有特殊的考虑。

kaz*_*ase 5

由于C字符串不多于0终止的字节数组,因此从Rust字符串转换非常简单。几乎每个有效的Rust字符串也都是有效的C字符串,但是您必须确保C字符串以0字符结尾,并且在字符串的其他任何地方都没有0字符。

Rust提供了负责转换的类型:CString

如果您的输入字符串已成功转换为a 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]
}
Run Code Online (Sandbox Code Playgroud)

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