簡體   English   中英

C ++和NTFS文件:路徑名VS開頭

[英]C++ and NTFS file: pathname VS opening

這是此問題的擴展: fstream不會打開路徑名帶有重音符號的文件

問題如下:一個程序打開一個簡單的NTFS文本文件,該文件的路徑名帶有重音符號 (例如àò ,...)。 在我的測試中我使用的是路徑文件I:\\ UNIVERSITA \\ foo.txt的 (UNIVERSITA是意大利大學的翻譯)

以下是測試程序:

#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的替代方案?

我希望可以使用通用路徑名打開通用文件,而無需使用特定於平台的代碼,但是我不知道該怎么做。

提前致謝。

你寫:

“源文件以UTF-8編碼保存。”

好吧(到目前為止),如果您使用的是g ++編譯器,該編譯器以UTF-8作為默認的基本源字符集。 但是,Visual C ++默認情況下將假定源文件是使用Windows ANSI編碼的,除非另有明確說明。 因此,請確保在開始時具有BOM(字節順序標記),據我所知,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,至少在Windows 7中,一切都可以與Visual C ++很好地配合使用:

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簡稱功能。 但是,這不是實際問題。 這只是意味着,如果用戶由於故意破壞了系統而遇到問題,則可以使用一個簡單的修復程序(即將其重新打開)。

您遇到的問題是,當path特定於實現時,傳遞給fstreams的編碼。 此外,程序的行為是實現定義的,因為它使用代碼中C ++字符集之外的字符,即重音字符。 那里的問題是,有許多不同的編碼可用於表示這些字符。

現在,有解決方案:

  • 首先,有一個MSC擴展來告訴編譯器應該采用哪種編碼。
  • 為了獲得使用CreateFileW()的路徑,可以對路徑進行編碼,例如wchar_t const path[] = {'f', 0x20ac, '.', 't', 'x', 't'}; 這並不是很舒服,但實際上,路徑是使用某些Unicode編碼或用戶輸入存儲在文件中的。
  • 然后,在標准庫的實現中進行了擴展,允許您使用wchar_t路徑,同時具有_wfopen()和fstream構造函數。
  • 然后是Boost,它具有專門用於提供可移植性的文件系統和iostream庫。 我一定會看這個。

請注意,盡管wchar_t路徑不可移植,但將它們移植到新平台通常不是很復雜。 幾個#ifdefs就可以了。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM