Чтение символов Unicode из аргументов командной строки с использованием boost::program_options в Windows

У меня есть несколько приложений Windows, которые считывают путь к файлу из аргументов командной строки. Все работает без нареканий, кроме случаев передачи путей с не-ANSI-символами. Я ожидал этого, но не знаю, как с этим бороться. Вероятно, вопрос начального уровня, но это сводит меня с ума.

Мой текущий код выглядит так:

int main(int argc, char* argv[]) {
    namespace po = boost::program_options;

    po::options_description po_desc("Allowed options");
    po_desc.add_options()
        ("file", po::value<std::string>(), "path to file");

    po::variables_map po_vm;
    try {
        po::store(po::parse_command_line(argc, argv, po_desc), po_vm);
        po::notify(po_vm);
    } catch (...) {
        std::cout << po_desc << std::endl;
        return false;
    }

    const std::string file_path = po_vm["file"].as<std::string>();

    // ...
}

Я обнаружил, что если я заменю тип file_path с std::string на boost::filesystem::path, некоторые пути теперь читаются. Я точно не знаю, почему, но могу сделать вывод, что это должно быть с переводом из кодировки Latin1.

Например, имея следующие файлы:

malaga.txt
málaga.txt
mąlaga.txt

Первый всегда читается правильно, а второй терпит неудачу при использовании std::string file_path, но не boost::filesystem::path file_path. Третий всегда терпит неудачу.

Я пробовал переключить основную функцию на int main(int argc, wchar_t* argv) и использовать std::wstring в качестве типа аргумента, но это несовместимо с парсером boost::program_options.

Как я могу правильно прочитать такие имена файлов Unicode?


person cbuchart    schedule 02.12.2019    source источник
comment
Вы читали Поддержка Unicode boost.org/doc/libs/1_71_0/doc/html/program_options/   -  person Richard Critten    schedule 02.12.2019
comment
Может быть, вам нужно сначала chcp 65001?   -  person daxim    schedule 02.12.2019
comment
Установка кодовой страницы консоли через chcp.com не имеет к этому никакого отношения. Родной командной строкой в ​​Windows является Unicode (UTF-16LE). Проблема в том, что точка входа main среды выполнения C анализирует кодировку ANSI командной строки из GetCommandLineA вместо командной строки Unicode из GetCommandLineW. Нестандартная точка входа wmain основана на собственной командной строке Unicode. Затем строки wchar_t могут быть закодированы как UTF-8 через WideCharToMultiByte, если приложению нужны байтовые строки.   -  person Eryk Sun    schedule 02.12.2019
comment
Спасибо всем, ваши комментарии были очень полезны!   -  person cbuchart    schedule 03.12.2019


Ответы (1)


Спасибо всем за комментарии, благодаря которым мне удалось решить мою проблему.

TL;DR

Вот фиксированный код:

int wmain(int argc, wchar_t* argv[]) { // <<<
    namespace po = boost::program_options;

    po::options_description po_desc("Allowed options");
    po_desc.add_options()
        ("file", po::wvalue<std::wstring>(), "path to file") // <<<
        ("ansi", po::value<std::string>(), "an ANSI string")
        ;

    po::variables_map po_vm;
    try {
        po::store(po::wcommand_line_parser(argc, argv) // <<<
                    .options(po_desc)
                    .run(),
                  po_vm);
        po::notify(po_vm);
    } catch (...) {
        std::cout << po_desc << std::endl;
        return false;
    }

    const boost::filesystem::path file_path = po_vm["file"].as<std::wstring>(); // <<<

    // ...
}

Объяснение

Сначала переключитесь на wmain и wchar_t* argv: как указано в @erik-sun, необходимо переключить точку входа к функции, поддерживающей Unicode. Важное замечание: можно использовать int main(int, wchar_t*) (в том смысле, что он скомпилируется), но он не получит аргументы с правильной кодировкой и синтаксический анализатор выйдет из строя, вы должны использовать wmain.

Затем Unicode ссылка поддержки, предоставленная @richard-critten, была очень полезна для понимания ошибок компиляции:

  • используйте boost::program_options::wvalue, когда тип широкоформатный. Внутренняя реализация использует строковый поток: по умолчанию он работает только с 8-битными символами.
  • используйте boost::program_options::wcommand_line_parser, чтобы принять wchar_t* аргументов. К сожалению, у этого класса нет универсального конструктора, и вы должны использовать форму long для синтаксического анализа командной строки.
  • наконец, при необходимости извлеките значение как std::wstring.

Я расширил фрагмент кода, чтобы показать, что он по-прежнему совместим с входными данными std::string.

Примечание

Мое полное решение требует создания экземпляра Qt QApplication в какой-то момент. Конструктор QApplication несовместим с широкоформатным argv. Поскольку с частью Qt не требуется взаимодействие с командной строкой (все обрабатывается задолго до того, как Boost) вы можете переписать, чтобы получить некоторые поддельные аргументы:

int fake_argc = 1;
char* fake_argv[] = {"AplicationName"};
QApplication a(fake_argc, fake_argv);
person cbuchart    schedule 03.12.2019