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

Я хочу читать / записывать файл с именем файла в юникоде, используя файловую систему 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 тоже не помогает, так как это просто обрабатывает широкий ввод.

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

Редактировать: Сообщение об ошибке опубликовано: https://svn.boost.org/trac/boost/ticket/9968

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

10

Решение

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

  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 C ++ 11), но это, вероятно, не поможет:

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

Чтобы Windows работала, вам, возможно, придется вызывать API-интерфейс Windows с поддержкой 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;
}
9

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

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

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

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

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

4

Рассматривали ли вы подход использования символов ASCII в исходном коде и использования возможностей форматирования Boost-сообщений библиотеки 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

2

РЕДАКТИРОВАТЬ: добавить ссылки на 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)) правильно работает, потому что Boost или Windows автоматически исправляет кодировку на входе.

Поэтому в Windows просто используйте редактор в кодовой странице 1252 или в латинице 1 или 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, которое позволило бы полный набор символов Юникода (или, по крайней мере, подмножество, предлагаемое 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 или что-то еще …

1