0

これは、この質問の拡張です: fstream がパス名にアクセント記号を含むファイルを開かない

問題は次のとおりです。プログラムが、パス名にアクセント記号が付いた単純な NTFS テキスト ファイル(例: àò、...) を開きます。私のテストでは、パス名が I:\università\foo.txt のファイルを使用しています( universitàUniversityのイタリア語訳です)

以下はテストプログラムです。

#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 エンコーディングで保存されます。」

デフォルトの基本ソース文字セットとして UTF-8 を持つ g++ コンパイラを使用している場合は、(これまでのところ) 問題ありません。ただし、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 文字列がルーチン階層に渡され、Windows の標準文字エンコーディングである UTF-16 に変換されます。結果はファイル名とまったく一致しません。


あなたはさらに尋ねます、

「2. CreateFileW() が g++ でのコンパイルでのみ機能するのはなぜですか?」

おそらく、ソース コード ファイルの先頭に BOM を含めていないためです (上記を参照)。

BOM を使用すると、少なくとも Windows 7 では、すべてが Visual C++ でうまく機能します。

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 ファイルシステムの回避策が削除された理由の 1 つは、私が理解しているように、少なくとも 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 コンストラクターの両方があります。
  • 次に、移植性を提供するために特別に作成されたファイルシステムと iostream ライブラリを持つ Boost があります。私は間違いなくこれを見ます。

wchar_t パスは移植可能ではありませんが、新しいプラットフォームへの移植は通常それほど複雑ではないことに注意してください。いくつかの #ifdefs で準備完了です。

于 2013-01-25T19:25:06.720 に答える