0

这是这个问题的一个扩展:fstream not opening files with peaks in pathname

问题如下:一个程序打开一个简单的 NTFS 文本文件,路径名中有重音符号(例如à , ò , ...)。在我的测试中,我使用了一个路径名为I:\università\foo.txt的文件 (università是大学的意大利语翻译

以下是测试程序:

#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!

那么让我们来看看问题:

  1. 为什么 fopen() 和 std::ifstream 在 Linux 中的类似测试中有效,但在 Windows 中无效?
  2. 为什么 CreateFileW() 只能使用 g++ 编译?
  3. 是否存在 CreateFile 的跨平台替代方案?

我希望无需特定平台的代码就可以打开具有通用路径名的通用文件,但我不知道该怎么做。

提前致谢。

4

2 回答 2

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 中是这样:

用 fopen 打开的文件!
用 c++ open() 打开的文件
使用 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 短名称功能。然而,这不是一个实际问题。这只是意味着如果用户由于故意对系统进行脑叶切除而遇到问题,则可以使用简单的修复程序(即重新打开它)。

于 2013-01-25T20:31:14.600 回答
1

您遇到的问题是您作为路径传递给 fstreams 的编码是特定于实现的。此外,您的程序的行为是实现定义的,因为它在代码中使用 C++ 字符集之外的字符,即重音字符。问题在于有许多不同的编码可以用来表示这些字符。

现在,有解决方案:

  • 首先,有一个 MSC 扩展来告诉编译器它应该采用哪种编码。
  • 为了获得使用 CreateFileW() 的路径,您可以对路径进行编码,如wchar_t const path[] = {'f', 0x20ac, '.', 't', 'x', 't'};. 这不是很舒服,但实际上路径存储在具有某些 Unicode 编码或用户输入的文件中。
  • 然后,标准库的实现中有一个扩展,允许您使用 wchar_t 路径,有 _wfopen() 和 fstream 构造函数。
  • 然后是 Boost,它有一个文件系统和 iostream 库,专门用于提供可移植性。我一定会看看这个。

请注意,虽然 wchar_t 路径不可移植,但将它们移植到新平台通常不是很复杂。几个#ifdefs,你就准备好了。

于 2013-01-25T19:25:06.720 回答