给定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());
}
有没有办法避免这么多中间步骤?
Path -> OsStr -> &str -> CString -> as_ptr()
答案 0 :(得分:7)
它并不像看起来那么容易。您没有提供的一条信息是: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上,它是' s实际上使用WTF-8,但这不是契约的;在Linux上,它只是字节数组。)
在将路径传递给C函数之前,您必须确定它所期望的字符串的编码方式,如果它与Rust的编码不匹配,您可以使用'在将它包裹在CString
之前必须将其转换。 Rust不允许您以独立于平台的方式将Path
或OsStr
转换为str
以外的任何内容。在基于Unix的目标上,OsStrExt
特征可用,并提供对OsStr
作为一个字节片段的访问。
Rust用于在OsStr
上提供to_cstring
方法,但它从未稳定,并且在Rust 1.6.0中已被弃用,因为它意识到该行为不适合Windows(它返回UTF-8编码路径,但Windows API不支持!)。
答案 1 :(得分:1)
由于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());
}
这是Unix。我不知道它在Windows下如何工作。
答案 2 :(得分:1)
如果您尝试制作def send_sms(request):
r = requests.post('https://api.msg91.com/api/v2/sendsms?country=91',json = payload, headers = headers)
print(r.text)
send_sms = r
if r.status_code == 200:
return HttpResponse('Its worked')
return render(request, 'index.html',{'ph' : send_sms})
,我通常会打电话给它并做:
Vec<u8>
充分了解非UNIX上的非UTF8路径将不受正确支持。请注意,如果使用Thrift /协议缓冲区而不是C API,则可能需要#[cfg(unix)]
fn path_to_bytes<P: AsRef<Path>>(path: P) -> Vec<u8> {
use std::os::unix::ffi::OsStrExt;
path.as_ref().as_os_str().as_bytes().to_vec()
}
#[cfg(not(unix))]
fn path_to_bytes<P: AsRef<Path>>(path: P) -> Vec<u8> {
// On Windows, could use std::os::windows::ffi::OsStrExt to encode_wide(),
// but you end up with a Vec<u16> instead of a Vec<u8>, so that doesn't
// really help.
path.as_ref().to_string_lossy().to_string().into_bytes()
}
。
答案 3 :(得分:1)
如果您的目标是将路径转换为某些字节序列,在任何编译代码的平台上,该路径都被解释为“本机”路径,那么最直接的方法是通过使用您要支持的每个平台的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());
}
此代码[1]为您提供了一个字节缓冲区,当您在linux上运行时,它表示路径为一系列以空值结尾的字节,而在Windows上运行时,则表示为“ unicode”(utf16)。您可以添加一个后备功能,在其他平台上将OsStr
转换为str
,但是我强烈建议您不要这样做。 (请参阅以下原因)
对于Windows,您需要先将缓冲区指针转换为wchar_t *
,然后再将其与Windows上的unicode函数一起使用(例如_wfopen
)。此代码假定wchar_t
大2个字节,并且缓冲区正确对齐了wchar_t
s。
在Linux端,只需按原样使用指针即可。
关于将路径转换为unicode字符串:不要。与这里和其他地方的建议相反,盲目地将路径转换为utf8并不是处理系统路径的正确方法。要求路径是有效的unicode会导致您的代码在(如果不是)遇到无效unicode的路径时失败。如果您要处理现实世界的路径,则 将不可避免地要处理非utf8路径。从长远来看,第一次正确做就可以避免很多痛苦和痛苦。
[1]:此代码直接从我正在使用的库中取出(可随时重用)。已通过wine在linux和64位Windows上进行了测试。