Правильно печатать символы 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, когда не было обнаружено совпадения языка (например, резервное копирование),
В качестве примера у вас есть ресурсы на немецком, чешском, русском и английском языках, а у пользователя есть китайский язык, он/она увидит английский плюс мусор вместо вашей красивой сделанной типографии, если вы сделаете свой текст красивым.