Правильно печатать символы 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);
}
РЕДАКТИРОВАТЬ: исправлены глупые опечатки и расшифровка строкового литерала, извините за те.