为什么 fstream 不支持文件名中的破折号?
Why doesn't fstream support an em-dash in the file name?
我将一些代码从 C 移植到 C++,并且刚刚发现包含 em-dash 的路径存在问题,例如"C:\temp\test—1.dgn"。即使路径在 Visual Studio 2005 调试器中正确显示,对 fstream::open() 的调用也会失败。
奇怪的是,使用 C 库 fopen() 函数的旧代码运行良好。我想我应该试试 wfstream class 的运气,然后发现使用 mbstowcs() 转换我的 C 字符串完全丢失了 em-dash,这意味着它也失败了。
我想这是一个语言环境问题,但为什么默认语言环境不支持 em-dash?为什么 fstream 不能处理 em-dash?我原以为 Windows 文件系统支持的任何字节字符都会被文件流 classes.
支持
考虑到这些限制,打开可能包含有效 Windows 文件名且不只是出现在某些字符上的文件流的正确方法是什么?
这应该可以工作,前提是您所做的一切 都是使用宽字符函数的宽字符表示法。也就是说,使用 wfstream,但不使用 mbstowcs,而是使用以 L
char:
为前缀的宽字符串文字
const wchar_t* filename = L"C:\temp\test—1.dgn";
此外,请确保您的源文件在 Visual Studio 中保存为 UTF-8。否则 em-dash 可能会出现区域设置问题。
字符 em-dash 在 UTF-16 中编码为 U+2014
(在 little endian 中编码为 0x14 0x20
),在 UTF-8 中编码为 0xE2 0x80 0x94
,以及其他代码或不编码全部取决于使用的字符集和代码页。 Windows-1252 代码页(在西欧语言中很常见)有破折号 0x97
我们可以认为是等效的。
Windows 在内部管理 UTF-16 路径,因此每次使用其错误调用的 ANSI 接口调用函数(以 A
结尾的函数)时,路径都会使用当前代码页进行转换为用户配置为 UTF-16。
另一方面,C和C++的RTL可以通过访问"ANSI"或"Unicode"(以W
结尾的函数)接口来实现。在第一种情况下,用于表示字符串的代码页必须与系统使用的代码页相同。第二种情况,要么我们一开始就直接使用utf-16字符串,要么必须将用于转换为utf-16的函数配置为使用与源字符串相同的代码页进行映射。
是的,这是一个复杂的问题。并且有几个错误(或有问题)的解决方案:
- 使用
wfstream
代替 fstream
: wfstream
不使用与 fstream
不同的路径。没有什么。它只是意味着 "manage the stream of bytes like wchar_t
"。 (它以一种与人们预期不同的方式做到这一点,因此在大多数情况下使这个 class 无用,但这是另一回事了)。要在 Visual Studio 实现中使用 Unicode 接口,它存在接受 const wchar_t*
的重载构造函数和 open()
函数。这些函数和构造函数为 fstream
和 wfstream
重载。使用 fstream
和右边的 open()
.
mbstowcs()
: 这里的问题是要使用的语言环境(其中包含字符串中使用的代码页)。如果您匹配区域设置是因为默认区域设置与系统匹配,那就太好了。如果没有,您可以尝试 mbstowcs_l()
。但是这些函数是不安全的 C 函数,所以你必须小心缓冲区大小。无论如何,只有在运行时获得要转换的路径时,这种方法才有意义。如果它是编译时已知的静态字符串,最好直接在代码中使用它。
L"C:\temp\test—1.dgn"
: 字符串中的 L
前缀并不意味着 "converts this string to utf-16" (源代码使用 8 位字符),至少在 Visual Studio 实现中没有。 L
前缀表示 "add a 0x00
byte after each character between the quotes"。所以—
,相当于窄(普通)字符串中的字节0x97
,在宽(前缀为L
)字符串中变成0x97 0x00
,而不是0x14 0x20
.相反,最好使用它的通用字符名称:L"C:\temp\test\u20141.dgn"
一种流行的方法是始终在您的代码中使用 utf-8 或 utf-16,并且仅在绝对必要时才进行转换。将具有特定代码页的字符串转换为 utf-8 或 utf-16 时,尝试首先转换为其中之一(utf-8 或 utf-16),首先识别正确的代码页。要进行该转换,请根据函数的来源使用这些函数。如果您从 XML 文件中获取字符串,那么,使用的代码页通常会在那里进行说明(并且曾经是 utf-8)。如果它来自 Windows 控件,请使用 Windows API 函数,如 MultiByteToWideChar
。 (CP_ACP
或 GetACP()
用作默认代码页)。
始终使用 fstream
(而不是 wfstream
)及其宽接口(open
和构造函数),而不是窄接口。 (您可以再次使用 MultiByteToWideChar
将 utf-8 转换为 utf-16)。
有几篇文章和 post 对这种方法提出了建议。我推荐给你的其中一个:http://www.nubaria.com/en/blog/?p=289.
为 运行 参与其中的其他人发布此解决方案。问题是 Windows 默认在启动时分配 "C" 语言环境,并且 em-dash (0x97) 在 "Windows-1252" 代码页中定义,但在正常的 ASCII [=27= 中未映射] 由 "C" 语言环境使用。所以简单的解决方案是调用:
setlocale ( LC_ALL, "" );
在 fstream::open 之前。这会将当前代码页设置为 OS 定义的代码页。在我的程序中,我想用fstream打开的文件是由用户定义的,所以它在系统定义的代码页中(Windows-1252)。
因此,虽然摆弄 unicode 和宽字符可能是避免未映射字符的解决方案,但这并不是问题的根源。实际问题是输入字符串的代码页 ("Windows-1252") 与 Windows 程序中默认使用的活动代码页 ("C") 不匹配。
我将一些代码从 C 移植到 C++,并且刚刚发现包含 em-dash 的路径存在问题,例如"C:\temp\test—1.dgn"。即使路径在 Visual Studio 2005 调试器中正确显示,对 fstream::open() 的调用也会失败。
奇怪的是,使用 C 库 fopen() 函数的旧代码运行良好。我想我应该试试 wfstream class 的运气,然后发现使用 mbstowcs() 转换我的 C 字符串完全丢失了 em-dash,这意味着它也失败了。
我想这是一个语言环境问题,但为什么默认语言环境不支持 em-dash?为什么 fstream 不能处理 em-dash?我原以为 Windows 文件系统支持的任何字节字符都会被文件流 classes.
支持考虑到这些限制,打开可能包含有效 Windows 文件名且不只是出现在某些字符上的文件流的正确方法是什么?
这应该可以工作,前提是您所做的一切 都是使用宽字符函数的宽字符表示法。也就是说,使用 wfstream,但不使用 mbstowcs,而是使用以 L
char:
const wchar_t* filename = L"C:\temp\test—1.dgn";
此外,请确保您的源文件在 Visual Studio 中保存为 UTF-8。否则 em-dash 可能会出现区域设置问题。
字符 em-dash 在 UTF-16 中编码为 U+2014
(在 little endian 中编码为 0x14 0x20
),在 UTF-8 中编码为 0xE2 0x80 0x94
,以及其他代码或不编码全部取决于使用的字符集和代码页。 Windows-1252 代码页(在西欧语言中很常见)有破折号 0x97
我们可以认为是等效的。
Windows 在内部管理 UTF-16 路径,因此每次使用其错误调用的 ANSI 接口调用函数(以 A
结尾的函数)时,路径都会使用当前代码页进行转换为用户配置为 UTF-16。
另一方面,C和C++的RTL可以通过访问"ANSI"或"Unicode"(以W
结尾的函数)接口来实现。在第一种情况下,用于表示字符串的代码页必须与系统使用的代码页相同。第二种情况,要么我们一开始就直接使用utf-16字符串,要么必须将用于转换为utf-16的函数配置为使用与源字符串相同的代码页进行映射。
是的,这是一个复杂的问题。并且有几个错误(或有问题)的解决方案:
- 使用
wfstream
代替fstream
:wfstream
不使用与fstream
不同的路径。没有什么。它只是意味着 "manage the stream of bytes likewchar_t
"。 (它以一种与人们预期不同的方式做到这一点,因此在大多数情况下使这个 class 无用,但这是另一回事了)。要在 Visual Studio 实现中使用 Unicode 接口,它存在接受const wchar_t*
的重载构造函数和open()
函数。这些函数和构造函数为fstream
和wfstream
重载。使用fstream
和右边的open()
. mbstowcs()
: 这里的问题是要使用的语言环境(其中包含字符串中使用的代码页)。如果您匹配区域设置是因为默认区域设置与系统匹配,那就太好了。如果没有,您可以尝试mbstowcs_l()
。但是这些函数是不安全的 C 函数,所以你必须小心缓冲区大小。无论如何,只有在运行时获得要转换的路径时,这种方法才有意义。如果它是编译时已知的静态字符串,最好直接在代码中使用它。L"C:\temp\test—1.dgn"
: 字符串中的L
前缀并不意味着 "converts this string to utf-16" (源代码使用 8 位字符),至少在 Visual Studio 实现中没有。L
前缀表示 "add a0x00
byte after each character between the quotes"。所以—
,相当于窄(普通)字符串中的字节0x97
,在宽(前缀为L
)字符串中变成0x97 0x00
,而不是0x14 0x20
.相反,最好使用它的通用字符名称:L"C:\temp\test\u20141.dgn"
一种流行的方法是始终在您的代码中使用 utf-8 或 utf-16,并且仅在绝对必要时才进行转换。将具有特定代码页的字符串转换为 utf-8 或 utf-16 时,尝试首先转换为其中之一(utf-8 或 utf-16),首先识别正确的代码页。要进行该转换,请根据函数的来源使用这些函数。如果您从 XML 文件中获取字符串,那么,使用的代码页通常会在那里进行说明(并且曾经是 utf-8)。如果它来自 Windows 控件,请使用 Windows API 函数,如 MultiByteToWideChar
。 (CP_ACP
或 GetACP()
用作默认代码页)。
始终使用 fstream
(而不是 wfstream
)及其宽接口(open
和构造函数),而不是窄接口。 (您可以再次使用 MultiByteToWideChar
将 utf-8 转换为 utf-16)。
有几篇文章和 post 对这种方法提出了建议。我推荐给你的其中一个:http://www.nubaria.com/en/blog/?p=289.
为 运行 参与其中的其他人发布此解决方案。问题是 Windows 默认在启动时分配 "C" 语言环境,并且 em-dash (0x97) 在 "Windows-1252" 代码页中定义,但在正常的 ASCII [=27= 中未映射] 由 "C" 语言环境使用。所以简单的解决方案是调用:
setlocale ( LC_ALL, "" );
在 fstream::open 之前。这会将当前代码页设置为 OS 定义的代码页。在我的程序中,我想用fstream打开的文件是由用户定义的,所以它在系统定义的代码页中(Windows-1252)。
因此,虽然摆弄 unicode 和宽字符可能是避免未映射字符的解决方案,但这并不是问题的根源。实际问题是输入字符串的代码页 ("Windows-1252") 与 Windows 程序中默认使用的活动代码页 ("C") 不匹配。