这是此问题的扩展:fstream not opening files with accent marks in pathname
问题如下:一个程序在路径名中打开一个带有重音符号的简单NTFS文本文件(例如à,ò ,. ..)。 在我的测试中,我使用的路径名为 I:\università\ foo.txt 的文件(università是 university 的意大利语翻译)< / p>
以下是测试程序:
#include <iostream>
#include <fstream>
#include <string>
#include <cstdio>
#include <errno.h>
#include <Windows.h>
using namespace std;
LPSTR cPath = "I:/università/foo.txt";
LPWSTR widecPath = L"I:/università/foo.txt";
string path("I:/università/foo.txt");
void tryWithStandardC();
void tryWithStandardCpp();
void tryWithWin32();
int main(int argc, char **argv) {
tryWithStandardC();
tryWithStandardCpp();
tryWithWin32();
return 0;
}
void tryWithStandardC() {
FILE *stream = fopen(cPath, "r");
if (stream) {
cout << "File opened with fopen!" << endl;
fclose(stream);
}
else {
cout << "fopen() failed: " << strerror(errno) << endl;
}
}
void tryWithStandardCpp() {
ifstream s;
s.exceptions(ifstream::failbit | ifstream::badbit | ifstream::eofbit);
try {
s.open(path.c_str(), ifstream::in);
cout << "File opened with c++ open()" << endl;
s.close();
}
catch (ifstream::failure f) {
cout << "Exception " << f.what() << endl;
}
}
void tryWithWin32() {
DWORD error;
HANDLE h = CreateFile(cPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) {
error = GetLastError();
cout << "CreateFile failed: error number " << error << endl;
}
else {
cout << "File opened with CreateFile!" << endl;
CloseHandle(h);
return;
}
HANDLE wideHandle = CreateFileW(widecPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (wideHandle == INVALID_HANDLE_VALUE) {
error = GetLastError();
cout << "CreateFileW failed: error number " << error << endl;
}
else {
cout << "File opened with CreateFileW!" << endl;
CloseHandle(wideHandle);
}
}
源文件以UTF-8编码保存。我正在使用Windows 8。
这是使用VC ++(Visual Studio 2012)
编译的程序的输出fopen() failed: No such file or directory
Exception ios_base::failbit set
CreateFile failed: error number 3
CreateFileW failed: error number 3
这是使用MinGW g ++的输出
fopen() failed: No such file or directory
Exception basic_ios::clear
CreateFile failed: error number 3
File opened with CreateFileW!
让我们回答一下问题:
我希望打开一个带有泛型路径名的通用文件,而不需要特定于平台的代码,但我不知道该怎么做。
提前致谢。
答案 0 :(得分:3)
你写道:
“源文件以UTF-8编码保存。”
如果您使用的是g ++编译器,它具有UTF-8作为其默认的基本源字符集,那么这一切都很好(到目前为止)。但是,默认情况下,Visual C ++将假定源文件使用Windows ANSI进行编码,除非另有说明。因此,请确保它在开始时有一个BOM(字节顺序标记),据我所知,它没有记录,导致Visual C ++将其视为使用UTF-8编码。
然后你问,
“1。为什么fopen()和std :: ifstream在Linux中的类似测试中工作,但它们不在Windows中?“
对于Linux来说,可能可以工作,因为(1)现代Linux是面向UTF-8的,所以如果文件名看起来相同,那么它可能与相同的UTF-8编码文件名相同源代码,(2)在* nix中,文件名只是一个字节序列,而不是一个字符序列。这意味着无论它看起来如何,如果你传递相同的字节序列,相同的值,那么你有一个匹配,否则不是。
相比之下,在Windows中,文件名是一系列可以以各种方式编码的字符。
在您的情况下,源代码中的UTF-8编码文件名在可执行文件中存储为Windows ANSI(是的,使用Visual C ++构建的结果取决于Windows中选定的ANSI代码页,这也是我知道是无证的)。然后将此gobbledegook字符串传递给例程层次结构并转换为UTF-16,这是Windows中的标准字符编码。结果与文件名完全不匹配。
你进一步问,
“2。为什么CreateFileW()只能用g ++编译?“
大概是因为你没有在源代码文件的开头包含BOM(见上文)。
使用BOM,一切都适用于Visual C ++,至少在Windows 7中是这样的:
File opened with fopen! File opened with c++ open() File opened with CreateFile!
最后,你问,
“3。是否存在CreateFile的跨平台替代方案?“
不是真的。有Boost文件系统。但是虽然它的版本2确实有标准库的有损窄字符编码的解决方法,但在版本3中删除了该解决方法,它只使用标准库的Visual C ++ 扩展,其中Visual C ++实现提供流构造函数和open
的宽字符参数版本。即,至少据我所知(我最近没有检查过事情是否已修复),Boost文件系统仅适用于Visual C ++,而不是例如g ++ - 虽然它适用于没有麻烦的字符文件名。
v2的解决方法是尝试转换为Windows ANSI(GetACP
函数指定的代码页),如果这不起作用,请尝试GetShortPathName
,这几乎可以保证用Windows ANSI表示。
根据我的理解,删除Boost文件系统中的解决方法的部分原因是,用户至少在Windows Vista及更早版本中关闭Windows短名称功能原则上是可能的。然而,这不是一个实际问题。它只是意味着如果用户由于故意对系统进行了切换而遇到问题,则可以轻松修复(即重新打开)。
答案 1 :(得分:1)
你遇到的问题是你作为路径传递给fstreams的编码是特定于实现的。此外,程序的行为是实现定义的,因为它在代码中使用C ++的字符集之外的字符,即重音字符。问题在于有许多不同的编码可用于表示这些字符。
现在,有解决方案:
wchar_t const path[] = {'f', 0x20ac, '.', 't', 'x', 't'};
那样对路径进行编码。这实际上并不舒服,但实际上路径存储在具有某种Unicode编码的文件中或由用户输入。请注意,虽然wchar_t路径不可移植,但将它们移植到新平台通常不是很复杂。一些#ifdefs,你已经设置好了。