如何在Windows上将OsStr转换为&[u8]/Vec<u8]?

16

我正在尝试将原始操作系统文件名持久化到存储器中,因此需要获取OsStr的原始字节。

在*nix平台上似乎可以调用as_bytes(),但在MS Windows上未定义。

是否有一种可移植的方法将OsStr转换为字节?


2
寻求调试帮助的问题(“为什么这段代码不起作用?”)必须在问题本身中包含所需的行为、具体问题或错误以及最短的代码,以便重现。没有明确问题陈述的问题对其他读者没有用处。请参阅:如何创建一个最小、完整和可验证的示例。 - Tatsuyuki Ishi
@TatsuyukiIshi,问题哪里不清楚?这不是关于调试的问题... - Alec
1
必须包括所需的行为、具体的问题或错误以及重现它所需的最短代码。 - Tatsuyuki Ishi
1
@TatsuyukiIshi 该函数不适用于Windows,即不存在。它是特定于操作系统的。我会为OP澄清问题。 - Alec
1
OsStr本身在内部使用的是WTF-8(即带代理项的UTF-8),即使在Windows上也一样,这只是一个实现细节。获取原始字节的唯一方法是通过将OsStr不安全地转换为&[u8],但不能保证始终有效。 - BurntSushi5
显示剩余3条评论
2个回答

8
在Rust 1.16中,没有定义获取Windows上OsStr字节的接口。实际的OsStr实现委托给特定于系统的代码。在*nix上,这是Vec<u8>包装器; 在Windows上,这是Wtf8Buf包装器。虽然Wtf8Buf是用Vec<u8>实现的,但不公开该实现细节。关于WTF-8的更多细节可在其网站上获取,其中包括以下引用,重点在于我:

在Windows(其API使用可能存在格式错误的UTF-16),Rust标准库在OS字符串上内部使用WTF-8,但不公开WTF-8字节序列

“问题”在于,在不同平台上,当涉及传递字符串到操作系统接口时,没有统一的“字符串”概念。在*nix上,接口通常接受类似UTF-8的内容,但不处理嵌入的NUL值。在Windows上,则取决于您是调用API的W还是A变体,尽管强烈推荐使用W变体。

这使得情况更加复杂,因为库也可能使用与操作系统不同的编码。如果您在Windows上使用在*nix上创建的C库,则几乎可以保证会采用伪UTF-8字符串,然后进行某种有损转换以调用正确的底层API。

Rust通过提供不透明类型OsStrOsString避免所有这些问题。

如果你需要将一个OsStr传递给一个接受UTF-8数据的函数,你需要将其转换为String&str,然后获取它的字节。如果你需要将其传递给一个接受LPCWSTR的函数,你首先需要将其转换为Vec<u16>,然后将指向该缓冲区的指针传递给Windows API。你可以看到Rust本身是如何做到这一点的示例

8
OsStr 的意义在于它的表示方式与操作系统有关。为了技术原因,实现有些复杂(@Shepmaster's answer提供了更多细节),但是你可以这样想:
  • 在 POSIX 系统上,OsStr 化为 & [u8],因为 POSIX 函数接受和返回字节字符串;
  • 在 Windows 上,OsStr 可以看作是一个 & [u16],因为 Win32 Unicode 函数将字符串作为 16 位单位的数组接受和返回。

由于本机 Windows API 接受 16 位的“宽字符”序列1,因此 OsStr 的设计目的是存储这种类型的字符。虽然 OsStr 可以转换为字节,就像任何东西都可以转换为字节一样,但这种表示对用户和系统都没有意义。这就是为什么 OsStr 在 Windows 上不提供检索内容作为字节的方法。但是,它确实提供了 OsStr::encode_wide(),该方法迭代底层的 u16 值,在 Win32 中非常有用。在另一个方向上,可以使用 OsString::from_wide()u16 值的片段创建 OsString

由你决定如何处理平台之间的这种差异。Rust 的 OsStr 提供了必要的工具来实现往返,但代码在不同平台上必然有所不同。例如,serde 通过有效地将作为 enum OsString { Unix(Vec<u8>), Windows(Vec<u16>) } 来解决差异。


1 Windows 宽字符字符串有时被描述为 UTF-16,因为这是它们在更高层次上的解释方式,但这并不适用于all操作系统字符串。Windows 文件名可以包含一对不是有效 UTF-16 的u16值,并仍然可用。这就是为什么不能将 Windows 字符串表示为字节,例如将其转换为 UTF-8。


据我所知,Windows和Java实际上使用UCS-2。 - Shepmaster
@Shepmaster 是的,那是相当正确的,但“UCS-2”这个术语可能会引起混淆,我想在答案中避免这种情况。UCS-2已经过时,不再被视为Unicode的编码(请参见此处的第900页)。它来自于Unicode受BMP限制的时代,联盟有很好的理由不鼓励使用它。Java文档小心翼翼地没有将编码命名为UCS-2,而是更喜欢提到UTF-16和BMP。 - user4815162342
1
OsStr 不可能是除了 UTF-8 超集以外的任何东西,因为 Rust 支持无分配零成本转换 &str&Path,以及 &Path&OsStr,所以每个有效的 &str 内存表示必须是一个有效的 &OsStr,而 UCS-2 并不符合这一点。 - Kornel
3
不是WTF-16,而是WTF-8,它是带有额外序列的UTF-8,用于破碎代理对。整个重点在于Rust已经揭示了这一事实,并使得使用16位内部表示的OsStr API无法实现。OsStr被默认保证始终以UTF-8的方式进行内部实现,并使用一些技巧(必须是&[u8],不能是&[u16]),否则它将无法满足所需的AsRef实现。 - Kornel
这些答案是错误的,但反映了Rust有多么糟糕。考虑尝试迭代一个目录,其名称来自运行Windows程序... Command :: new(...).output()。unwrap()。stdout()返回Vec <u8>,但std :: fs :: read_dir需要std :: path :: Path,该路径从OsStr构造。祝你将前者转换为后者好运... OsStrExt :: from_bytes仅在Unix上可用。如果像OsString这样的抽象在每个操作系统上都无法使用,那有什么意义呢? - Jim Balter
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接