我需要执行一个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;
}
但这感觉不对。
编辑,我认为这与this并不完全相同,因为我的问题与字符串有关,并且IIRC在将&str转换为CString时需要特别注意。
答案 0 :(得分:4)
由于C字符串不多于0终止的字节数组,因此从Rust字符串转换非常简单。几乎每个有效的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()]
会惊慌。如果从C调用f
,这是您要避免的事情。