Файл Visual C ++ и NTFS: открытие пути VS

Это продолжение этого вопроса: 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!

Итак, давайте перейдем к вопросам:

  1. Почему fopen () и std :: ifstream работают в похожем тесте в Linux, но не в Windows?
  2. Почему CreateFileW () работает только при компиляции с g ++?
  3. Существует ли кроссплатформенная альтернатива CreateFile?

Я надеюсь, что открытие общий файл с общий путь может быть сделан без необходимости специфичного для платформы кода, но я не знаю, как это сделать.

Заранее спасибо.

0

Решение

Ты пишешь:

«Исходный файл сохраняется в кодировке 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 и более ранних версиях. Однако это не практическая проблема. Это просто означает, что доступно простое исправление (а именно, включить его), если пользователь испытывает проблемы из-за преднамеренной лоботомии системы.

3

Другие решения

Проблема, над которой вы спотыкаетесь, заключается в том, что кодировка, которую вы передаете в fstreams как путь, зависит от конкретной реализации. Кроме того, поведение вашей программы определяется реализацией, потому что в коде используются символы вне набора символов C ++, то есть акцентированные символы. Проблема в том, что существует много разных кодировок, которые можно использовать для представления этих символов.

Теперь есть решения:

  • Во-первых, есть расширение MSC, чтобы сообщить компилятору, какую кодировку он должен принимать.
  • Чтобы получить путь, работающий с CreateFileW (), вы можете закодировать путь следующим образом wchar_t const path[] = {'f', 0x20ac, '.', 't', 'x', 't'};, Это не очень удобно, но на практике пути хранятся в файлах с некоторой кодировкой Unicode или вводятся пользователем.
  • Затем в реализации стандартной библиотеки есть расширение, которое позволяет вам использовать пути wchar_t, есть как _wfopen (), так и конструкторы fstream.
  • Затем есть Boost, который имеет файловую систему и библиотеку iostream, специально созданную для обеспечения переносимости. Я бы определенно посмотрел на это.

Обратите внимание, что хотя пути wchar_t не являются переносимыми, их перенос на новую платформу обычно не очень сложен. Несколько #ifdefs и все готово.

1

По вопросам рекламы [email protected]