Чтение/запись файла с именем файла Unicode с помощью простого C++/Boost

Я хочу прочитать/записать файл с именем файла Unicode, используя файловую систему boost, локаль boost в Windows (mingw) (в конце должно быть независимым от платформы).

Это мой код:

#include <boost/locale.hpp>
#define BOOST_NO_CXX11_SCOPED_ENUMS
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
namespace fs = boost::filesystem;

#include <string>
#include <iostream>

int main() {

  std::locale::global(boost::locale::generator().generate(""));
  fs::path::imbue(std::locale());

  fs::path file("äöü.txt");
  if (!fs::exists(file)) {
    std::cout << "File does not exist" << std::endl;
  }

  fs::ofstream(file, std::ios_base::app) << "Test" << std::endl;
}

fs::exists действительно проверяет наличие файла с именем äöü.txt. Но записанный файл имеет имя äöü.txt.

Чтение дает ту же проблему. Использование fs::wofstream тоже не помогает, так как это просто обрабатывает широкий ввод.

Как я могу исправить это с помощью С++ 11 и повысить?

Изменить: Отчет об ошибке опубликован: https://svn.boost.org/trac/boost/ticket/9968

Чтобы уточнить награду: это довольно просто с Qt, но я хотел бы кроссплатформенное решение, использующее только C++11 и Boost, без Qt и без ICU.


person Mike M    schedule 30.04.2014    source источник
comment
На самом деле, учитывая äöü.txt, похоже, что литерал уже UTF8, за исключением того, что boost::fs::path обрабатывает его так, как если бы это была CodePage 1252. Или, что более вероятно, boost::fs::path вообще игнорирует кодировку и просто передает ОС , и ОС предполагает, что это кодовая страница 1252.   -  person Mooing Duck    schedule 30.04.2014
comment
Перечитывая вопрос, fs::exists работает, значит, ошибка должна быть в boost::fs::ofstream. Я предполагаю, что это обнаруживает, что вы компилируете с помощью GCC и поэтому неправильно решаете передать ОС имя файла в кодировке UTF8. Это будет ошибка повышения. (Ответ был удален, но уточненная OP проблема идентична для широкого строкового литерала)   -  person Mooing Duck    schedule 30.04.2014
comment
Возможно, äöü не входят в исходный набор символов; попробуйте заменить их эквивалентными шестнадцатеричными литералами (я предполагаю, что вы имеете в виду версии этих символов, которые можно хранить в 8-битном символе).   -  person M.M    schedule 03.05.2014
comment
Но тогда почему fs::exists работает? Это действительно похоже на проблему с потоками файловой системы, поэтому я ищу решение без них или исправление для них.   -  person Mike M    schedule 03.05.2014
comment
Я тестировал Ubuntu 12.04 с boost v1.48, проблема не воспроизводится. Возможно, вы можете проверить, какую версию Boost вы используете, и посмотреть, исправлена ​​ли она уже или это проблема mingw.   -  person Mine    schedule 04.05.2014
comment
Я использую Boost 1.55. Как я увижу, если это проблема mingw?   -  person Mike M    schedule 04.05.2014
comment
В какой кодировке находится ваш исходный файл и какая у вас системная кодировка? Если бы вы написали такую ​​простую программу, как int main() { std::cout << "äöü"; return 0; }, что бы получилось на выходе? (включает опущено для ясности...)   -  person Serge Ballesta    schedule 09.05.2014
comment
Кодировка исходного файла может быть любой, но, скорее всего, это будет UTF8. Но я не понимаю, почему содержимое имеет значение для имени файла. Системная кодировка - это то, что использует пользователь, поскольку мне нужно, чтобы она не зависела от платформы. В настоящее время я тестирую Windows, поэтому cp 1252.   -  person Mike M    schedule 09.05.2014


Ответы (4)


Это может быть сложно по двум причинам:

  1. В исходном файле C++ есть строка, отличная от ASCII. То, как этот литерал преобразуется в двоичное представление const char *, будет зависеть от настроек компилятора и/или настроек кодовой страницы ОС.

  2. Windows работает только с именами файлов Unicode через кодировку UTF-16, в то время как Unix использует UTF-8 для имен файлов Unicode.

Создание объекта пути

Чтобы это работало в Windows, вы можете попробовать изменить литерал на широкие символы (UTF-16):

const wchar_t *name = L"\u00E4\u00F6\u00FC.txt";
fs::path file(name);

Чтобы получить полное кросс-платформенное решение, вам нужно начать со строки UTF-8 или UTF-16, а затем убедиться, что она правильно преобразована в класс path::string_type.

Открытие файлового потока

К сожалению, C++ (и, следовательно, Boost) ofstream API не позволяет указывать wchar_t строк в качестве имени файла. Это касается как конструктора, так и open метод.

Вы можете попытаться убедиться, что объект пути не будет немедленно преобразован в const char * (с помощью строкового API С++ 11), но это, вероятно, не поможет:

std::ofstream(file.native()) << "Test" << std::endl;

Чтобы Windows работала, вам, возможно, придется вызывать Windows API с поддержкой Unicode, CreateFileW, преобразуйте HANDLE в FILE *, затем используйте FILE * для конструктора ofstream. Все это описано в другом ответе StackOverflow, но я не уверен, что этот конструктор ofstream будет существовать в MinGW.

К сожалению, basic_ofstream, по-видимому, не позволяет создавать подклассы для пользовательских типов basic_filebuf, поэтому преобразование FILE * может быть единственным (полностью не переносимым) вариантом.

Альтернатива: файлы с отображением памяти

Вместо использования файловых потоков вы также можете записывать в файлы с помощью ввода-вывода с отображением памяти< /а>. В зависимости от того, как Boost реализует это (это не часть стандартной библиотеки C++), этот метод может работать с именами файлов Windows Unicode.

Вот пример повышения (взято из другого ответа), в котором для открытия файла используется объект path:

#include <boost/filesystem.hpp>
#include <boost/iostreams/device/mapped_file.hpp>
#include <iostream>

int main()
{ 
  boost::filesystem::path p(L"b.cpp");
  boost::iostreams::mapped_file file(p); // or mapped_file_source
  std::cout << file.data() << std::endl;
}
person Dan Cecile    schedule 09.05.2014
comment
Поскольку fs::exists находит нужный файл, не означает ли это, что строка корректно преобразуется в path::string_type? - person Mike M; 09.05.2014
comment
Я обновил свой ответ. Глядя на доступные API-интерфейсы C++, перспективы не очень хорошие... - person Dan Cecile; 09.05.2014
comment
Спасибо за подробный ответ, мне нужно его пройти. - person Mike M; 09.05.2014
comment
Большое спасибо, сопоставленный файл делает свое дело. Есть ли недостаток в его использовании по сравнению с чтением и записью файлов напрямую с файловыми потоками? - person Mike M; 11.05.2014
comment
Недостатки заключаются в том, что вам нужно изменить размер файла (до правильного размера) перед его открытием, и что любые ошибки чтения/записи после открытия файла будут отображаться как SIGBUS в Unix и EXCEPTION_IN_PAGE_ERROR в Windows, что приведет к завершению вашего приложения, если оно не обработано;) - person Dan Cecile; 12.05.2014

Я не знаю, как здесь был принят ответ, поскольку OP fs::path::imbue(std::locale()); точно не заботится о кодовой странице ОС, std::wstring, а что нет. В противном случае да, он просто использовал бы старый добрый iconv, вызовы Winapi или другие вещи, предложенные в принятом ответе. Но не в этом смысл использования boost::locale.

Реальный ответ, почему это не работает, хотя OP делает imbue() текущую локаль, как указано в документации Boost (см. "Кодировка по умолчанию в Microsoft Windows") из-за ошибок boost (или mingw), которые остаются нерешенными в течение как минимум пару лет по состоянию на март 2015.

К сожалению, пользователи mingw, похоже, остались в стороне.

Теперь, что должны сделать разработчики Boost, чтобы покрыть эти ошибки, это совсем другой вопрос. Может оказаться, что им нужно реализовать именно то, что сказал Дэн.

person rr-    schedule 08.03.2015

Рассматривали ли вы подход с использованием символов ASCII в исходном коде и использованием возможностей Boost Messages Formatting библиотеки Boost.Locale для поиска нужной строки с использованием ключа ASCII? http://www.boost.org/doc/libs/1_55_0/libs/locale/doc/html/messages_formatting.html

В качестве альтернативы вы можете использовать библиотеку Boost.Locale для создания библиотеки UTF-8, а затем наполнить Boost.Path этой локалью, используя « boost::path::imbue ()». http://boost.2283326.n4.nabble.com/boost-filesystem-path-as-utf-8-td4320098.html

Это также может быть полезно для вас.

Кодировка по умолчанию в Microsoft Windows http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/default_encoding_under_windows.html

person Ben Key    schedule 10.05.2014

РЕДАКТИРОВАТЬ: добавить ссылки на boost и wchar_t в конце сообщения и другое возможное решение для Windows.

Я мог бы воспроизвести почти одно и то же на Ubuntu и на Windows, даже не используя Boost (у меня его нет на моем окне Windows). Чтобы исправить это, мне просто нужно было преобразовать исходник в ту же кодировку, что и в системе, т.е. utf8 в Ubuntu и latin1 или iso-8859-1 в Windows.

Как я и подозревал, проблема исходит из строки fs::path file("äöü.txt");. Поскольку кодировка файла отличается от ожидаемой, он более или менее читается как fs::path file("äöü.txt");. Под вашим контролем вы обнаружите, что размер равен 10. Это полностью объясняет, что выходной файл имеет неправильное имя.

Подозреваю, что тест if (!fs::exists(file)) работает корректно, потому что либо буст, либо винда автоматически исправляет кодировку при вводе.

Итак, в Windows просто используйте редактор с кодовой страницей 1252, latin1 или iso-8859-1, и у вас не должно возникнуть проблем, если вам не нужно использовать символы вне этой кодировки. Если вам нужны символы за пределами Latin1, я боюсь, что вам придется использовать Unicode API Windows.

РЕДАКТИРОВАТЬ:

На самом деле Windows (> NT) изначально работает с wchar_t, а не с char. И неудивительно, что boost для Windows делает то же самое — см. ссылка на файловую систему библиотеки ускорения. Извлекать :

Для Windows-подобных реализаций, включая MinGW, path::value_type — это wchar_t. Встроенная локаль по умолчанию предоставляет аспект codecvt, который вызывает Windows MultiByteToWideChar или WideCharToMultiByte API с кодовой страницей CP_THREAD_ACP, если Windows AreFileApisANSI() имеет значение true ...

Таким образом, другое решение в Windows, которое позволило бы использовать полный набор символов Unicode (или, по крайней мере, подмножество, изначально предлагаемое Windows), состояло бы в том, чтобы указать путь к файлу как wstring, а не как string. В качестве альтернативы, если вы действительно хотите использовать имена файлов в кодировке UTF8, вам придется заставить локаль потока использовать UTF8, а не CP1252. Я не могу привести пример этого кода, потому что у меня нет бустера на моем окне Windows, на моем окне Windows работает старая XP и не поддерживается UTF8, и я не хочу публиковать непроверенный код, но я думаю, что в этом случае вы должен заменить

std::locale::global(boost::locale::generator().generate(""));

с чем-то вроде:

std::locale::global(boost::locale::generator().generate("UTF8"));

ВНИМАНИЕ: не проверено, поэтому я не уверен, является ли строка для генерации UTF8 или чем-то еще...

person Serge Ballesta    schedule 09.05.2014
comment
Размер внутренней строки равен 10, так как строка содержит представление utf8 длиной 10 байт. Необходимость сохранять файлы в разных кодировках для меня невозможна, так как тогда моя программа не будет кроссплатформенной. - person Mike M; 11.05.2014
comment
Я имел в виду, что в Windows вы получаете имя файла, закодированное в UTF8, вы должны сообщить системе, что это не кодировка Ansi, потому что это значение по умолчанию. Если вам нужна константа Unicode, переносимая между Windows и Linux, вы должны использовать широкие символы, как предложил Дэн Сесиль. Кстати, после того, как вы покопались в документе boost, я думаю, вам следует попробовать в своем приложении fs::imbue(std::locale()); вместо fs::path::imbue(std::locale()); сообщить всем модулям Boost FileSystem, какая текущая локаль - даже если она уже должна быть по умолчанию, поскольку вы берете ее из boost::locale::generator().generate(""). - person Serge Ballesta; 11.05.2014
comment
Установка глобального языкового стандарта и наполнение пути должны сообщать объекту пути, что полученная им строка имеет кодировку UTF8. И fs::exists просто отлично работает таким образом. Файлы с отображением памяти, которые я использую вместе с предложенным путем, также находят правильный файл. Итак, я думаю, что это возможно без широких символов. - person Mike M; 11.05.2014