我正在尝试在 C++ 库上编写一个 Rust 包装器,其中指向 C 样式字符串数组的指针定义为:
char ***name;
在 C++ 中,我可以轻松地迭代字符串:
for(int i=0;i<n;++i)
std::cout << std::string(*(name[i])) << std::endl;
给出正确的结果。
但是,在 Rust 中我不知道如何正确地做到这一点。
与 C++ 库的绑定由 bindgen 正确生成,并且
name
显示为
pub name: *mut *mut *mut ::std::os::raw::c_char
所以,我正在尝试以下方法:
// Get a slice of pointers to strings of size n, n is known and is correct
// The type of names is &[*mut i8]
let names = unsafe{ std::slice::from_raw_parts(*(name), n) };
// Iterates over a slice. ptr: &*mut i8
for ptr in names.iter() {
// Constructs a c-string from *mut i8
let cs = unsafe{ CStr::from_ptr(*ptr) };
// Shoud print a string, but gives rubbish instead
println!("{:?}",cs);
}
但是这会打印垃圾,所有输出行都相同且大小为 2,而字符串不同且长度不同:
"\x90\""
"\x90\""
"\x90\""
"\x90\""
"\x90\""
"\x90\""
我肯定遗漏了一些明显的东西,但找不到任何错误。如有任何帮助,我们将不胜感激!
您的 Rust 代码似乎是正确的。 这是对情况的模拟,内存布局与您的描述相符。
也许C端的实际布局并不完全是你描述的那样。 在我看来,第一级参考(指针)是不需要的,但我不知道您的用例的实际需要。
use std::ffi::{c_char, CStr};
fn prepare_name_vec() -> Vec<*const c_char> {
["one\0", "two\0", "three\0", "four\0"] // static nul-terminated strings
.iter()
.map(|s| s.as_ptr() as *const _) // u8 ptr to u8 ptr
.collect()
}
fn read_names_from_c(
names: *const *const *const c_char,
n: usize,
) {
/*
names *names c_str
char *** •~~> char ** •~~> [char * •~~> ['o', 'n', 'e', '\0']
,char * •~~> ['t', 'w', 'o', '\0']
,char * •~~> ['t', 'h', 'r', 'e', 'e', '\0']
char * ] •~~> ['f', 'o', 'o', 'r', '\0']
*/
let name_slice = unsafe { std::slice::from_raw_parts(*names, n) };
for c_ptr in name_slice.iter() {
let c_str = unsafe { CStr::from_ptr(*c_ptr) };
println!("{:?}", c_str);
}
}
fn main() {
let name_vec = prepare_name_vec();
let names: *const *const *const c_char = &name_vec.as_ptr() as *const _;
let n = name_vec.len();
read_names_from_c(names, n);
}