给定a std::path::Path,将此转换为空终止的最直接方法是std::os::raw::c_char什么?(用于传递到采用路径的C函数).
use std::ffi::CString;
use std::os::raw::c_char;
use std::os::raw::c_void;
extern "C" {
some_c_function(path: *const c_char);
}
fn example_c_wrapper(path: std::path::Path) {
let path_str_c = CString::new(path.as_os_str().to_str().unwrap()).unwrap();
some_c_function(path_str_c.as_ptr());
}
Run Code Online (Sandbox Code Playgroud)
有没有办法避免这么多中间步骤?
Path -> OsStr -> &str -> CString -> as_ptr()
Run Code Online (Sandbox Code Playgroud)
它并不像看起来那么容易.有一条你没有提供的信息:C函数期望路径是什么编码?
在Linux上,路径是"仅"字节数组(0表示无效),应用程序通常不会尝试解码它们.(但是,他们可能必须使用特定的编码对它们进行解码,例如将它们显示给用户,在这种情况下,他们通常会尝试根据当前的区域设置解码它们,这通常会使用UTF-8编码.)
在Windows上,它更复杂,因为有许多API函数使用"ANSI"代码页和使用"Unicode"(UTF-16)的变体.此外,Windows不支持将UTF-8设置为"ANSI"代码页.这意味着除非库特别期望UTF-8并将路径转换为本机编码本身,否则传递UTF-8编码路径肯定是错误的(尽管它似乎适用于仅包含ASCII字符的字符串).
(我不知道其他平台,但它已经足够混乱了.)
在Rust中,Path它只是一个包装器OsStr.OsStr当字符串确实是有效的UTF-8时,使用与UTF-8兼容的平台相关表示,但非UTF-8字符串使用未指定的编码(在Windows上,它实际上使用的是WTF-8,但这是不合同;在Linux上,它只是字节数组.
在将路径传递给C函数之前,必须确定它期望字符串所在的编码,如果它与Rust的编码不匹配,则必须先将其转换为包装CString.Rust不允许您以独立于平台的方式将a Path或an 转换为OsStr除a之外的任何内容str.在基于Unix的目标上,该OsStrExt特征是可用的,并提供OsStr对字节切片的访问.
Rust用于提供to_cstring方法OsStr,但它从未稳定,并且在Rust 1.6.0中已被弃用,因为它意识到该行为不适合Windows(它返回了UTF-8编码路径,但Windows API没有'支持!).
由于Path只是围绕 的一个薄包装OsStr,您几乎可以将其按原样传递给您的 C 函数。但要成为有效的 C 字符串,我们必须添加 NUL 终止字节。因此我们必须分配一个CString.
在另一方面,转换到一个str既危险(如果什么Path不是有效的UTF-8字符串?)和不必要的成本:我用as_bytes()的不是to_str()。
fn example_c_wrapper<P: AsRef<std::path::Path>>(path: P) {
let path_str_c = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
some_c_function(path_str_c.as_ptr());
}
Run Code Online (Sandbox Code Playgroud)
这是针对 Unix 的。我不知道它在 Windows 上是如何工作的。
如果您的目标是将路径转换为在编译代码的任何平台上被解释为“本机”路径的某个字节序列,那么最直接的方法是使用OsStrExt您想要支持的每个平台的:
let path = ..;
let mut buf = Vec::new();
#[cfg(unix)] {
use std::os::unix::ffi::OsStrExt;
buf.extend(path.as_os_str().as_bytes());
buf.push(0);
}
#[cfg(windows)] {
use std::os::windows::ffi::OsStrExt;
buf.extend(path.as_os_str()
.encode_wide()
.chain(Some(0))
.map(|b| {
let b = b.to_ne_bytes();
b.get(0).map(|s| *s).into_iter().chain(b.get(1).map(|s| *s))
})
.flatten());
}
Run Code Online (Sandbox Code Playgroud)
此代码 [1] 为您提供了一个字节缓冲区,当您在 linux 上运行它时,它将路径表示为一系列以空字符结尾的字节,并在 Windows 上运行时表示“unicode”(utf16)。您可以添加在其他平台上转换OsStr为 a的后备str,但我强烈建议不要这样做。(看下面的原因)
对于 Windows,wchar_t *在 Windows 上将缓冲区指针与 unicode 函数一起使用之前,您需要将其强制转换为(例如_wfopen)。此代码假定swchar_t为两个字节大,并且缓冲区已正确对齐到wchar_ts。
在 Linux 端,只需按原样使用指针。
关于将路径转换为 unicode 字符串:不要. 与此处和其他地方的建议相反,盲目地将路径转换为 utf8 并不是处理系统路径的正确方法。要求路径是有效的 unicode 将导致您的代码在(不是如果)遇到无效的 unicode 路径时失败。如果您正在处理现实世界的路径,您将不可避免地处理非 utf8 路径。从长远来看,第一次做对将有助于避免很多痛苦和痛苦。
[1]:这段代码是直接从我正在处理的库中取出的(可以随意重用)。它已经通过 wine 在 linux 和 64 位 Windows 上进行了测试。