【问题标题】:Whats the most direct way to convert a Path to a *c_char?将路径转换为 ​​c_char 的最直接方法是什么?
【发布时间】:2016-12-21 06:43:40
【问题描述】:

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

【问题讨论】:

  • 假设Path 可以转换为C 字符串是不准确的。平台可以并且确实使用不同的编码;这就是为什么这些抽象首先存在的原因。如果您仅限于类 UNIX 系统,则有 OsStrExt
  • 另请注意,您还要转换为 String,它必须是 UTF-8,尽管 C 字符串不需要这样做。

标签: string rust ffi


【解决方案1】:

这并不像看起来那么容易。您没有提供一条信息:期望路径所在的 C 函数的编码是什么?

在 Linux 上,路径“只是”字节数组(0 无效),应用程序通常不会尝试解码它们。 (但是,他们可能必须使用特定的编码对它们进行解码,例如将它们显示给用户,在这种情况下,他们通常会尝试根据当前的语言环境对它们进行解码,这通常会使用 UTF-8 编码。)

在 Windows 上,它更复杂,因为有使用“ANSI”代码页的 API 函数的变体和使用“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 的编码不匹配,则必须在将其包装到 @987654329 之前对其进行转换@。 Rust 不允许您以独立于平台的方式将 PathOsStr 转换为 str 以外的任何内容。在基于 Unix 的目标上,OsStrExt trait 可用,并提供对 OsStr 作为字节切片的访问。

Rust 曾经在 OsStr 上提供 to_cstring 方法,但它从未稳定过,并且在 Rust 1.6.0 中被弃用,因为它意识到该行为不适合 Windows(它返回一个 UTF- 8 编码路径,但 Windows API 不支持!)。

【讨论】:

  • Locale 可能是对 linux 的系统猜测,但它与路径编码并没有真正的关系。路径可以是除 0 以外的任意字节。
【解决方案2】:

由于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 上是如何工作的。

【讨论】:

    【解决方案3】:

    如果您的目标是将路径转换为在编译代码的任何平台上被解释为“本机”路径的一些字节序列,那么最直接的方法是通过使用您要支持的每个平台的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 上运行它时,它将路径表示为一系列以 null 结尾的字节,并在 Windows 上运行时表示“unicode”(utf16)。您可以在其他平台上添加一个将OsStr 转换为str 的回退,但我强烈建议不要这样做。 (见下文)

    对于 Windows,您需要先将缓冲区指针转换为 wchar_t *,然后再将其与 Windows 上的 unicode 函数一起使用(例如 _wfopen)。此代码假定wchar_t 是两个字节大,并且缓冲区与wchar_ts 正确对齐。

    在 Linux 端,只需按原样使用指针。

    关于将路径转换为 ​​unicode 字符串:不要。与此处和其他地方的建议相反,盲目地将路径转换为 ​​utf8 并不是处理系统路径的正确方法。要求路径是有效的 unicode 将导致您的代码在(而不是如果)遇到无效 unicode 的路径时失败。如果您正在处理真实世界的路径,那么您不可避免地要处理非 utf8 路径。从长远来看,第一次做对将有助于避免很多痛苦和痛苦。

    [1]:此代码直接取自我正在开发的库(请随意重用)。它已经通过 wine 对 linux 和 64 位 windows 进行了测试。

    【讨论】:

      【解决方案4】:

      如果您想生成Vec&lt;u8&gt;,我通常会打电话给它并这样做:

      #[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()
      }
      

      完全清楚非 UNIX 上的非 UTF8 路径将不被正确支持。请注意,如果使用 Thrift/协议缓冲区而不是 C API,您可能需要 Vec&lt;u8&gt;

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-05-29
        • 2015-05-15
        • 2012-02-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多