什么是将Path转换为* c_char的最直接方法?

时间:2016-08-15 02:48:13

标签: string rust ffi

给定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()

4 个答案:

答案 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不允许您以独立于平台的方式将PathOsStr转换为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上进行了测试。