Правильно печатать символы 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);
}

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

Ответ 1

Другой трюк вместо 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);

Ответ 2

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

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

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

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

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

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

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 - это не просто другой файл, который принимает поток байтов. Это специальное устройство, созданное и принадлежащее программе и доступное через собственный уникальный API WIN32. Проблема в том, что когда консоль написана, API видит точно объем данных, переданных при использовании своего API, и переход от узких символов к широким символам происходит без учета того, что данные могут быть неполными. Когда многобайтовый символ передается с использованием более одного вызова API-интерфейса консоли, каждая отдельно переданная часть рассматривается как незаконная кодировка и рассматривается как таковая.

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

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

Ответ 3

//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

Ответ 4

Консоль может быть настроена для отображения символов 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 результат, или принудительно fflush только накопленный выходной буфер.

Если все сбой

Обратите внимание на формат 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 это ужас.

Ответ 5

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

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

Ответ 6

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

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);
}

EDIT: фиксированные глупые опечатки и декодирование строкового литерала, извините за них.

Ответ 7

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

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

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

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

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