Как выводить строки Unicode на консоли Windows

уже есть несколько вопросов, связанных с этой проблемой. Я думаю, что мой вопрос немного другой, потому что у меня нет реальной проблемы, я просто спрашиваю академических интересов. Я знаю, что реализация UTF-16 в Windows иногда противоречит стандарту Unicode (например, сортировке) или ближе к старой UCS-2, чем к UTF-16, но я буду придерживаться терминологии "UTF-16" по причинам простота.

Фон: в Windows все UTF-16. Независимо от того, имеете ли вы дело с ядром, графической подсистемой, файловой системой или любым другим, вы передаете строки UTF-16. В смысле Unix нет локалей или кодировок. Для совместимости со средневековыми версиями Windows есть вещь, называемая "кодовыми страницами", которая устарела, но тем не менее поддерживается. AFAIK существует только одна правильная и не устаревшая функция для записи строк в консоль, а именно WriteConsoleW, которая принимает строку UTF-16. Кроме того, аналогичное обсуждение применяется к входным потокам, которые я также игнорирую.

Однако я считаю, что это представляет собой недостаток дизайна в Windows API: существует общая функция, которая может использоваться для записи во все объекты потока (файлы, трубы, консоли...) под названием WriteFile, но эта функция является байтом -ориентированный и не принимает строки UTF-16. В документации предлагается использовать WriteConsoleW для вывода на консоль, который ориентирован на текст, и WriteFile для всего остального, байт-ориентированного. Поскольку потоки консоли и файловые объекты представлены обработчиками объектов ядра, а потоки консолей можно перенаправить, вы должны вызвать функцию для каждой записи в стандартный выходной поток, который проверяет, является ли дескриптор консольным потоком или файлом, нарушая полиморфность. OTOH, я думаю, что разделение Windows между текстовыми строками и необработанными байтами (которое отражается во многих других системах, таких как Java или Python) концептуально превосходит подход Unix char*, который игнорирует кодировки и не различает строки и массивы байтов.

Итак, мои вопросы: что делать в этой ситуации? И почему эта проблема не решена даже в собственных библиотеках Microsoft? Как библиотеки .NET Framework, так и библиотеки C и С++, похоже, придерживаются устаревшей модели кодировки. Как бы вы разработали API Windows или инфраструктуру приложения, чтобы обойти эту проблему?

Я думаю, что общая проблема (что нелегко решить) состоит в том, что все библиотеки предполагают, что все потоки байт-ориентированы, а поверх них также используются текстовые потоки. Однако мы видим, что Windows имеет специальные текстовые потоки на уровне ОС, и библиотеки не могут справиться с этим. Поэтому в любом случае мы должны внести существенные изменения во все стандартные библиотеки. Быстрым и грязным способом было бы рассматривать консоль как специальный поток, ориентированный на байты, который принимает только одну кодировку. Это по-прежнему требует, чтобы стандартные библиотеки C и С++ были обойдены, поскольку они не реализуют переключатель WriteFile/WriteConsoleW. Это правильно?

Ответ 1

Общая стратегия, которую мы используем в большинстве (кросс-платформенных) приложениях/проектах, такова: мы просто используем UTF-8 (я имею в виду реальный стандарт) везде. Мы используем std::string как контейнер, и мы просто интерпретируем все как UTF8. И мы также обрабатываем весь файл IO таким образом, то есть ожидаем UTF8 и сохраняем UTF8. В случае, когда мы получаем строку откуда-то и знаем, что это не UTF8, мы преобразуем ее в UTF8.

Наиболее распространенным случаем, когда мы натыкаемся на WinUTF16, является имя файла. Поэтому для каждой обработки имен файлов мы всегда будем преобразовывать строку UTF8 в WinUTF16. А также другим способом, если мы будем искать каталог для файлов.

Консоль на самом деле не используется в нашей сборке Windows (в сборке Windows весь вывод консоли завернут в файл). Поскольку у нас есть UTF8 везде, также наш консольный выход - UTF8, что отлично подходит для большинства современных систем. А также файл журнала консоли Windows имеет свой контент в UTF8, и большинство текстовых редакторов в Windows могут читать это без проблем.

Если мы будем использовать WinConsole больше, и если нам очень понравится, что все специальные символы отображаются правильно, мы, возможно, напишем некоторый автоматический обработчик каналов, который мы установим между fileno=0 и реальным stdout, который будет использовать WriteConsoleW, как вы предположили (если нет более простого способа).

Если вы задаетесь вопросом, как реализовать такой автоматический обработчик каналов: мы уже реализовали такую ​​вещь для всех POSIX-подобных систем. Код, вероятно, не работает на Windows, как есть, но я думаю, что его можно будет портировать. Наш текущий обработчик труб похож на то, что делает tee. То есть если вы выполните cout << "Hello" << endl, он будет напечатан на stdout и в некотором лог файле. Посмотрите код, если вам интересно, как это делается.

Ответ 2

Несколько точек:

  • Одним из важных различий между Windows "WriteConsoleW" и printf является то, что WriteConsoleW смотрит на консоль как графический интерфейс, а скорее текстовый. Например, если вы используете его и используете канал, вы не будете записывать вывод.
  • Я бы никогда не сказал, что кодовые страницы устарели. Может быть, разработчики окон хотели бы, чтобы они были такими, но они никогда не были бы такими. Весь мир, но windows api, использует байтовые ориентированные потоки для представления данных: XML, HTML, HTTP, Unix и т.д., И т.д. Используют кодировки, а самый популярный и самый мощный - UTF-8. Таким образом, вы можете использовать Wide string внутри, но во внешнем мире вам нужно что-то еще.

    Даже когда вы печатаете wcout << L"Hello World" << endl, это преобразованный под капотом в байт ориентированный поток, на большинстве систем (но окна) к UTF-8.

  • Мое личное мнение, Microsoft допустила ошибку, изменив свой API в любом месте до широкого, а не везде, где поддерживается UTF-8. Конечно, вы можете спорить об этом. Но на самом деле вам нужно разделить потоки текста и байта и преобразовать между ними.

Ответ 3

Чтобы ответить на ваш первый вопрос, вы можете выводить строки Unicode на консоль Windows, используя _ setmode. Подробные сведения об этом можно найти на блоге Майкла Каплана. По умолчанию консоль не является Unicode (UCS-2/UTF-16). Он работает на основе Ansi (locale/code page) и должен быть специально настроен для использования Unicode.

Кроме того, вы должны изменить шрифт консоли, поскольку шрифт по умолчанию поддерживает только символы Ansi. Здесь есть некоторые незначительные исключения, такие как нулевые расширенные символы ASCII, но для печати фактических символов Юникода требуется использование _setmode.

В Windows все UTF-16. Независимо от того, имеете ли вы дело с ядром, графической подсистемой, файловой системой или любым другим, вы передаете строки UTF-16. В смысле Unix нет локалей или кодировок.

Это не совсем так. В то время как базовое ядро ​​Windows использует Unicode, в игру входит огромное количество интероперабельности, которая позволяет Windows взаимодействовать с широким спектром программного обеспечения.

Рассмотрим блокнот (да, блокнот далек от основного компонента, но он имеет мою точку зрения). Блокнот имеет возможность читать файлы, содержащие Ansi (текущая кодовая страница), Unicode или UTF-8. Вы можете рассматривать блокнот как приложение Unicode, но это не совсем точно.

Лучшим примером являются драйверы. Драйверы могут быть записаны в Unicode или Ansi. Это действительно зависит от характера интерфейса. Для дальнейшего использования Microsoft предоставляет библиотеку StrSafe, которая была специально написана с помощью Kernel-mode drivers, и он включает как Unicode, так и Ansi версии. Хотя драйверы либо Ansi, либо Unicode, ядро ​​Windows должно взаимодействовать с ними - правильно - независимо от того, какую форму они принимают.

Чем дальше вы попадаете из ядра Windows, тем больше интероперабельность вступает в игру. Это включает кодовые страницы и локали. Вы должны помнить, что не все программное обеспечение написано с помощью Unicode. Visual С++ 2010 по-прежнему обладает способностью использовать с помощью Ansi, Multi-Byte или Unicode. Это включает в себя использование кодовых страниц и локалей, которые часть стандарта C/С++.

Однако, я думаю, что это представляет собой дефект дизайна в Windows API

эти две статьи обсуждают это довольно хорошо.

Итак, мои вопросы: что делать в этой ситуации? И почему эта проблема не решена даже в собственных библиотеках Microsoft? Как библиотеки .NET Framework, так и библиотеки C и С++, похоже, придерживаются устаревшей модели кодировки. Как бы вы разработали API Windows или инфраструктуру приложения, чтобы обойти эту проблему?

В этот момент, я думаю, вы смотрите на Windows в задним числом. Unicode не был первым, ASCII. После ASCII пришли кодовые страницы. После кодовых страниц появилась DBCS. После того, как DBCS пришел MBCS (и в конечном итоге UTF-8). После UTF-8 пришел Unicode (UTF-16/UCS-2).

Каждая из этих технологий была включена в ОС Windows на протяжении многих лет. Каждое здание на последнем, но не нарушая друг друга. Программное обеспечение было написано с учетом каждого из них. Хотя иногда это может показаться не так, Microsoft ставит огромное количество усилий в не нарушение программного обеспечения, которое он не писал. Даже сейчас вы можете написать новое программное обеспечение, которое использует любую из этих технологий, и оно будет работать.

Реальный ответ здесь - "совместимость". Microsoft по-прежнему использует эти технологии, а также многие другие компании. Существует огромное количество программ, компонентов и библиотек, которые не были обновлены (или когда-либо будут обновлены) для использования Unicode. Даже когда появляются новые технологии - например,.NET - старые технологии должны придерживаться. По крайней мере, для интероперабельности.

Например, скажем, что у вас есть DLL, с которой вам нужно взаимодействовать с .NET, но эта DLL была написана с использованием Ansi (однобайтная кодовая страница, локализованная). Чтобы усугубить ситуацию, у вас нет источника для DLL. Единственный ответ здесь - использовать эти устаревшие функции.

Ответ 4

Как я исправляю работу следующим образом:

  • Использовать UTF-16 и wchar_t внутренне, это хорошо работает с именами файлов и Windows API в целом.
  • Установить кодовую страницу до 65001, которая является UTF-8. Это гарантирует, что при чтении файлов открытого текста окна проверяют их на UTF-16 и спецификацию ( "стандарт Windows" ), а если нет спецификации, текст будет обрабатываться как UTF-8 ( "мировой стандарт" ) и переведен к UTF-16 для вашего использования.