Правильно печатать символы utf8 в консоли Windows

Вот как я пытаюсь это сделать:

#include <stdio.h>
#include <windows.h>
using namespace std;

int main() {
  SetConsoleOutputCP(CP_UTF8);
   //german chars won't appear
  char const* text = "aäbcdefghijklmnoöpqrsßtuüvwxyz";
  int len = MultiByteToWideChar(CP_UTF8, 0, text, -1, 0, 0);
  wchar_t *unicode_text = new wchar_t[len];
  MultiByteToWideChar(CP_UTF8, 0, text, -1, unicode_text, len);
  wprintf(L"%s", unicode_text);
}

И эффект в том, что отображаются только мы ascii символов. Ошибки не отображаются. Исходный файл закодирован в utf8.

Итак, что я здесь делаю не так?

в WouterH:

int main() {
  SetConsoleOutputCP(CP_UTF8);
  const wchar_t *unicode_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
  wprintf(L"%s", unicode_text);
}
  • это тоже не работает. Эффект все тот же. Мой шрифт, конечно, Lucida Console.

третий дубль:

#include <stdio.h>
#define _WIN32_WINNT 0x05010300
#include <windows.h>
#define _O_U16TEXT  0x20000
#include <fcntl.h>

using namespace std;

int main() {
    _setmode(_fileno(stdout), _O_U16TEXT);
    const wchar_t *u_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
    wprintf(L"%s", u_text);
}

хорошо, что-то начинает работать, но вывод: ańbcdefghijklmno÷pqrs▀tuŘvwxyz,

7 ответов

По умолчанию широкие функции печати в Windows не обрабатывают символы вне диапазона ascii.

Есть несколько способов получить данные Unicode на консоль Windows.

  • используйте консольный API напрямую, WriteConsoleW. Вы должны будете убедиться, что вы действительно пишете на консоль, и использовать другие средства, когда вывод относится к чему-то другому.

  • установите режим стандартных дескрипторов выходного файла на один из режимов 'Unicode', _O_U16TEXT или _O_U8TEXT. Это заставляет функции вывода широких символов правильно выводить данные Unicode на консоль Windows. Если они используются в файловых дескрипторах, которые не представляют консоль, то они приводят к тому, что выходной поток байтов будет UTF-16 и UTF-8 соответственно. Обратите внимание, что после установки этих режимов функции нешироких символов в соответствующем потоке становятся непригодными и приводят к падению. Вы должны использовать только функции широких символов.

  • Текст UTF-8 можно распечатать непосредственно на консоли, установив кодовую страницу вывода консоли на CP_UTF8, если вы используете правильные функции. Большинство функций более высокого уровня, таких как basic_ostream<char>::operator<<(char*) не работают таким образом, но вы можете использовать функции более низкого уровня или реализовать собственный ostream, который решает проблему стандартных функций.

Проблема с третьим методом заключается в следующем:

putc('\302'); putc('\260'); // doesn't work with CP_UTF8

puts("\302\260"); // correctly writes UTF-8 data to Windows console with CP_UTF8 

В отличие от большинства операционных систем, консоль в Windows - это не просто другой файл, который принимает поток байтов. Это специальное устройство, созданное и принадлежащее программе и доступное через собственный уникальный WIN32 API. Проблема заключается в том, что при записи в консоль API точно видит объем данных, передаваемых при использовании его API, и преобразование из узких символов в широкие символы происходит без учета того, что данные могут быть неполными. Когда многобайтовый символ передается с использованием более одного вызова API консоли, каждый отдельно передаваемый фрагмент рассматривается как недопустимая кодировка и рассматривается как таковой.

Обойти это должно быть достаточно легко, но команда CRT в Microsoft считает, что это не их проблема, тогда как любая команда, работающая с консолью, вероятно, не заботится.

Вы можете решить эту проблему, внедрив свой собственный подкласс streambuf, который правильно выполняет преобразование в wchar_t. Т.е. с учетом того факта, что байты многобайтовых символов могут приходить отдельно, поддерживая состояние преобразования между записями (например, std::mbstate_t).

Еще один трюк вместо SetConsoleOutputCP, будет использовать _setmode на stdout:

// Includes needed for _setmode()
#include <io.h>
#include <fcntl.h>

int main() {
    _setmode(_fileno(stdout), _O_U16TEXT);  
    wchar_t * unicode_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
    wprintf(L"%s", unicode_text);
    return 0;
}

Не забудьте убрать звонок SetConsoleOutputCP(CP_UTF8);

//Save As UTF8 without signature
#include<stdio.h>
#include<windows.h>
int main() {
  SetConsoleOutputCP(65001);
  const char unicode_text[]="aäbcdefghijklmnoöpqrsßtuüvwxyz";
  printf("%s\n", unicode_text);
}

Результат:
aäbcdefghijklmnoöpqrsßtuüvwxyz

У меня были похожие проблемы, но ни один из существующих ответов не помог мне. Что-то еще я заметил, что, если я вставлю символы UTF-8 в простой строковый литерал, они будут печататься правильно, но если я попытаюсь использовать литерал UTF-8 (u8"text") символы обрабатываются компилятором (что подтверждается печатью их числовых значений по одному байту за раз; необработанный литерал имеет правильные байты UTF-8, что проверено на компьютере с Linux, но литерал UTF-8 был мусором),

После некоторого возни, я нашел решение: /utf-8, С этим все просто работает; мои источники - UTF-8, я могу использовать явные литералы UTF-8, и вывод работает без каких-либо других изменений.

Консоль может быть настроена на отображение символов UTF-8: @vladasimovic ответы SetConsoleOutputCP(CP_UTF8) может быть использовано для этого. Кроме того, вы можете подготовить консоль с помощью команды DOS chcp 65001 или системным вызовом system("chcp 65001 > nul") в основной программе. Не забудьте также сохранить исходный код в UTF-8.

Чтобы проверить поддержку UTF-8, запустите

#include <stdio.h>
#include <windows.h>

BOOL CALLBACK showCPs(LPTSTR cp) {
  puts(cp);
  return true;
}

int main() {
  EnumSystemCodePages(showCPs,CP_SUPPORTED);
}

65001 должен появиться в списке.

Консоль Windows по умолчанию использует кодовые страницы OEM, и большинство растровых шрифтов по умолчанию поддерживают только национальные символы. Windows XP и новее также поддерживают шрифты TrueType, которые должны отображать пропущенные символы (@Devenec предлагает Lucida Console в своем ответе).

Почему printf терпит неудачу

Как указывает @bames53 в своем ответе, консоль Windows не является потоковым устройством, вам нужно записать все байты многобайтового символа. Иногда printf портит работу, помещая байты в буфер вывода один за другим. Попробуй использовать sprintf а потом puts результат или принудительно очищать только накопленный выходной буфер.

Если все не удается

Обратите внимание на формат UTF-8: один символ отображается как 1-5 байтов. Используйте эту функцию для перехода к следующему символу в строке:

const char* ucshift(const char* str, int len=1) {
  for(int i=0; i<len; ++i) {
    if(*str==0) return str;
    if(*str<0) {
      unsigned char c = *str;
      while((c<<=1)&128) ++str;
    }
    ++str;
  }
  return str;
}

... и эта функция для преобразования байтов в число Unicode:

int ucchar(const char* str) {
  if(!(*str&128)) return *str;
  unsigned char c = *str, bytes = 0;
  while((c<<=1)&128) ++bytes;
  int result = 0;
  for(int i=bytes; i>0; --i) result|= (*(str+i)&127)<<(6*(bytes-i));
  int mask = 1;
  for(int i=bytes; i<6; ++i) mask<<= 1, mask|= 1;
  result|= (*str&mask)<<(6*bytes);
  return result;
}

Затем вы можете попробовать использовать некоторые дикие / древние / нестандартные функции winAPI, такие как MultiByteToWideChar (не забудьте вызвать setlocale() до!)

или вы можете использовать свое собственное отображение из таблицы Unicode в вашу активную рабочую кодовую страницу. Пример:

int main() {
  system("chcp 65001 > nul");
  char str[] = "příšerně"; // file saved in UTF-8
  for(const char* p=str; *p!=0; p=ucshift(p)) {
    int c = ucchar(p);
    if(c<128) printf("%c\n",c);
    else printf("%d\n",c);
  }
}

Это должно напечатать

p
345
237
353
e
r
n
283

Если ваша кодовая страница не поддерживает эту чешскую взаимосвязь, вы можете отобразить 345=>r, 237=>i, 353=>s, 283=>e. Есть только 5(!) Разных кодировок только для чешского языка. Отображать читаемые символы в разных локалях Windows - это ужас.

UTF-8 не работает для консоли Windows. Период. Я перепробовал все комбинации без успеха. Проблемы возникают из-за различного назначения символов ANSI/OEM, поэтому некоторые ответы говорят, что проблем нет, но такие ответы могут исходить от программистов, использующих 7-битный простой ASCII, или иметь идентичные кодовые страницы ANSI/OEM (китайский, японский).

Либо вы используете UTF-16 и функции широких символов (но вы по-прежнему ограничены 256 символами вашей кодовой страницы OEM - за исключением китайского / японского), либо вы используете строки ASCII кодовой страницы OEM в вашем исходном файле.

Да, это вообще беспорядок.

Для многоязычных программ я использую строковые ресурсы и написал LoadStringOem() функция, которая автоматически переводит ресурс UTF-16 в строку OEM, используя WideCharToMultiByte() без промежуточного буфера. Поскольку Windows автоматически выбирает правильный язык из ресурса, мы надеемся, что она загрузит строку на языке, который можно преобразовать в целевую кодовую страницу OEM.

Как следствие, вы не должны использовать 8-битные типографские символы для англо-американского языкового ресурса (как многоточие… и кавычки ""), так как англо-американский выбран Windows, когда совпадение языков не обнаружено (т. Е. Откат). Например, у вас есть ресурсы на немецком, чешском, русском и англо-американском языках, а у пользователя - китайский, он / она увидит английский плюс мусор вместо хорошо сделанной типографики, если вы сделали текст красивым.

Я решил проблему следующим образом:

Lucida Console, похоже, не поддерживает умлауты, поэтому изменение шрифта консоли на Consolas, например, работает.

#include <stdio.h>
#include <Windows.h>

int main()
{
    SetConsoleOutputCP(CP_UTF8);

    // I'm using Visual Studio, so encoding the source file in UTF-8 won't work
    const char* message = "a" "\xC3\xA4" "bcdefghijklmno" "\xC3\xB6" "pqrs" "\xC3\x9F" "tu" "\xC3\xBC" "vwxyz";

    // Note the capital S in the first argument, when used with wprintf it
    // specifies a single-byte or multi-byte character string (at least on
    // Visual C, not sure about the C library MinGW is using)
    wprintf(L"%S", message);
}

РЕДАКТИРОВАТЬ: исправлены глупые опечатки и расшифровка строкового литерала, извините за те.

Другие вопросы по тегам