C ++和NTFS文件:pathname VS opening

时间:2013-01-25 18:11:26

标签: c++ visual-c++ mingw ntfs

这是此问题的扩展: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!

让我们回答一下问题:

  1. 为什么fopen()和std :: ifstream在Linux中进行类似的测试,但它们不在Windows中?
  2. 为什么CreateFileW()只能用g ++编译?
  3. 是否存在CreateFile的跨平台替代方案?
  4. 我希望打开一个带有泛型路径名的通用文件,而不需要特定于平台的代码,但我不知道该怎么做。

    提前致谢。

2 个答案:

答案 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 ++的字符集之外的字符,即重音字符。问题在于有许多不同的编码可用于表示这些字符。

现在,有解决方案:

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

请注意,虽然wchar_t路径不可移植,但将它们移植到新平台通常不是很复杂。一些#ifdefs,你已经设置好了。