Почему некоторые символы Unicode приводят к отказу std:: wcout в консольном приложении?

Рассмотрим следующий фрагмент кода, скомпилированный как консольное приложение в MS Visual Studio 2010/2012 и выполненный на Win7:

#include "stdafx.h"
#include <iostream>
#include <string>


const std::wstring test = L"hello\xf021test!";

int _tmain(int argc, _TCHAR* argv[])
{
    std::wcout << test << std::endl;
    std::wcout << L"This doesn't print either" << std::endl;

    return 0;
}

Первая инструкция wcout выводит "привет" (вместо "hello? test!" ) Второй вывод wcout ничего не выводит.

Как будто 0xf021 (и другие?) символы Unicode приводят к сбою wcout.

Этот конкретный символ Юникода, 0xf021 (закодированный как UTF-16), является частью "Частной области использования" на базовой многоязычной плоскости. Я заметил, что приложения Windows Console не имеют расширенной поддержки символов Unicode, но обычно каждый символ, по крайней мере, представлен символом по умолчанию (например, "?" ), Даже если нет поддержки для отображения определенного глифа.

Что вызывает подавление потока wcout? Есть ли способ reset после входа в это состояние?

Ответ 1

wcout, или, если быть точным, экземпляр wfilebuf, который он использует внутри, преобразует широкие символы в узкие символы, а затем записывает их в файл (в вашем случае - stdout). Преобразование выполняется фасет codecvt в локали потока; по умолчанию, это просто wctomb_s, преобразование в кодовую страницу ANSI по умолчанию, aka CP_ACP.

По-видимому, символ '\xf021' не отображается в кодовой странице по умолчанию, настроенной в вашей системе. Таким образом, преобразование завершается с ошибкой, а в потоке задано failbit. Как только failbit установлен, все последующие вызовы немедленно завершаются.

Я не знаю, как получить wcout, чтобы успешно печатать произвольные символы Unicode для консоли. wprintf работает, хотя и немного подстраивается:

#include <fcntl.h>
#include <io.h>
#include <string>

const std::wstring test = L"hello\xf021test!";

int _tmain(int argc, _TCHAR* argv[])
{
  _setmode(_fileno(stdout), _O_U16TEXT);
  wprintf(test.c_str());

  return 0;
}

Ответ 2

Настройка режима для stdout на _O_U16TEXT позволит вам писать символы Unicode в поток wcout, а также wprintf. (См. Обычная мудрость задерживается, aka What the @#% & * _O_U16TEXT?) Это правильный путь, чтобы сделать эту работу.

_setmode(_fileno(stdout), _O_U16TEXT);

std::wcout << L"hello\xf021test!" << std::endl;
std::wcout << L"\x043a\x043e\x0448\x043a\x0430 \x65e5\x672c\x56fd" << std::endl;
std::wcout << L"Now this prints!" << std::endl;

Больше не нужно, но вы можете reset поток, который ввел состояние ошибки, вызвав clear:

if (std::wcout.fail())
{
    std::wcout.clear();
}