Это продолжение этого вопроса: fstream не открывает файлы с отметками в имени пути
Проблема заключается в следующем: программа, открывающая простой текстовый файл 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!
Итак, давайте перейдем к вопросам:
Я надеюсь, что открытие общий файл с общий путь может быть сделан без необходимости специфичного для платформы кода, но я не знаю, как это сделать.
Заранее спасибо.
Ты пишешь:
«Исходный файл сохраняется в кодировке UTF-8».
Ну, это все хорошо (пока), если вы используете компилятор g ++, у которого UTF-8 является базовым исходным набором символов по умолчанию. Однако Visual C ++ по умолчанию будет предполагать, что исходный файл закодирован в Windows ANSI, если явно не указано иное. Поэтому убедитесь, что в начале у него есть BOM (Byte Order Mark), что, насколько я знаю, недокументировано, заставляет 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 ++ зависит от выбранной кодовой страницы ANSI в Windows, которая, насколько я знаю, не имеет документов). ). Затем эта строка gobbledegook передается по обычной иерархии и преобразуется в UTF-16, который является стандартной кодировкой символов в Windows. Результат не соответствует имени файла вообще.
Вы еще спросите,
«2. Почему CreateFileW () работает только при компиляции с g ++? »
Предположительно, потому что вы не включили спецификацию в начале файла кода источника (см. Выше).
С 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, по крайней мере, в Windows Vista и более ранних версиях. Однако это не практическая проблема. Это просто означает, что доступно простое исправление (а именно, включить его), если пользователь испытывает проблемы из-за преднамеренной лоботомии системы.
Проблема, над которой вы спотыкаетесь, заключается в том, что кодировка, которую вы передаете в fstreams как путь, зависит от конкретной реализации. Кроме того, поведение вашей программы определяется реализацией, потому что в коде используются символы вне набора символов C ++, то есть акцентированные символы. Проблема в том, что существует много разных кодировок, которые можно использовать для представления этих символов.
Теперь есть решения:
wchar_t const path[] = {'f', 0x20ac, '.', 't', 'x', 't'};
, Это не очень удобно, но на практике пути хранятся в файлах с некоторой кодировкой Unicode или вводятся пользователем.Обратите внимание, что хотя пути wchar_t не являются переносимыми, их перенос на новую платформу обычно не очень сложен. Несколько #ifdefs и все готово.