UTF-8 вывод на консоль Windows - PullRequest
       47

UTF-8 вывод на консоль Windows

7 голосов
/ 02 ноября 2009

Следующий код демонстрирует неожиданное поведение на моем компьютере (протестировано с Visual C ++ 2008 SP1 в Windows XP и VS 2012 в Windows 7):

#include <iostream>
#include "Windows.h"

int main() {
    SetConsoleOutputCP( CP_UTF8 );
    std::cout << "\xc3\xbc";
    int fail = std::cout.fail() ? '1': '0';
    fputc( fail, stdout );
    fputs( "\xc3\xbc", stdout );
}

Я просто скомпилировал с cl /EHsc test.cpp.

Windows XP: Вывод в окне консоли ü0ü (переведено на кодовую страницу 1252, первоначально показывает некоторые линии символы в кодовой странице по умолчанию, возможно, 437). Когда я меняю настройки окна консоли, чтобы использовать набор символов "Lucida Console" и запустить мой test.exe снова выводится на , что означает

  • символ ü может быть записан с использованием fputs и его кодировки UTF-8 C3 BC
  • std::cout не работает по любой причине
  • потоки failbit устанавливаются после попытки ввода символа

Windows 7: Вывод с использованием Consolas - ��0ü. Еще интереснее. Возможно, записаны правильные байты (по крайней мере, при перенаправлении вывода в файл), и состояние потока в порядке, но два байта записаны как отдельные символы).

Я пытался поднять эту проблему в «Microsoft Connect» (см. здесь ), но MS не очень помог. С таким же успехом вы можете посмотреть здесь как что-то подобное уже спрашивали.

Можете ли вы воспроизвести эту проблему?

Что я делаю не так? Разве std::cout и fputs не должны иметь одинаковые эффект?

решено: (в некотором роде) Следуя идее mike.dld, я реализовал std::stringbuf, выполняя преобразование из UTF-8 в Windows-1252 в sync() и заменил потоковый буфер std::cout этот конвертер (см. мой комментарий к ответу mike.dld).

Ответы [ 3 ]

4 голосов
/ 12 февраля 2014

Я понимаю, что вопрос довольно старый, но если кому-то все равно будет интересно, ниже мое решение. Я реализовал довольно простой потомок std :: streambuf и затем передал его каждому стандартному потоку в самом начале выполнения программы.

Это позволяет вам использовать UTF-8 везде в вашей программе. При вводе данные берутся из консоли в Unicode, а затем конвертируются и возвращаются вам в UTF-8. На выходе производится обратное: получение данных от вас в UTF-8, преобразование их в Unicode и отправка на консоль. Пока проблем не найдено.

Также обратите внимание, что это решение не требует какой-либо модификации кодовой страницы: SetConsoleCP, SetConsoleOutputCP или chcp, или что-то еще.

Это буфер потока:

class ConsoleStreamBufWin32 : public std::streambuf
{
public:
    ConsoleStreamBufWin32(DWORD handleId, bool isInput);

protected:
    // std::basic_streambuf
    virtual std::streambuf* setbuf(char_type* s, std::streamsize n);
    virtual int sync();
    virtual int_type underflow();
    virtual int_type overflow(int_type c = traits_type::eof());

private:
    HANDLE const m_handle;
    bool const m_isInput;
    std::string m_buffer;
};

ConsoleStreamBufWin32::ConsoleStreamBufWin32(DWORD handleId, bool isInput) :
    m_handle(::GetStdHandle(handleId)),
    m_isInput(isInput),
    m_buffer()
{
    if (m_isInput)
    {
        setg(0, 0, 0);
    }
}

std::streambuf* ConsoleStreamBufWin32::setbuf(char_type* /*s*/, std::streamsize /*n*/)
{
    return 0;
}

int ConsoleStreamBufWin32::sync()
{
    if (m_isInput)
    {
        ::FlushConsoleInputBuffer(m_handle);
        setg(0, 0, 0);
    }
    else
    {
        if (m_buffer.empty())
        {
            return 0;
        }

        std::wstring const wideBuffer = utf8_to_wstring(m_buffer);
        DWORD writtenSize;
        ::WriteConsoleW(m_handle, wideBuffer.c_str(), wideBuffer.size(), &writtenSize, NULL);
    }

    m_buffer.clear();

    return 0;
}

ConsoleStreamBufWin32::int_type ConsoleStreamBufWin32::underflow()
{
    if (!m_isInput)
    {
        return traits_type::eof();
    }

    if (gptr() >= egptr())
    {
        wchar_t wideBuffer[128];
        DWORD readSize;
        if (!::ReadConsoleW(m_handle, wideBuffer, ARRAYSIZE(wideBuffer) - 1, &readSize, NULL))
        {
            return traits_type::eof();
        }

        wideBuffer[readSize] = L'\0';
        m_buffer = wstring_to_utf8(wideBuffer);

        setg(&m_buffer[0], &m_buffer[0], &m_buffer[0] + m_buffer.size());

        if (gptr() >= egptr())
        {
            return traits_type::eof();
        }
    }

    return sgetc();
}

ConsoleStreamBufWin32::int_type ConsoleStreamBufWin32::overflow(int_type c)
{
    if (m_isInput)
    {
        return traits_type::eof();
    }

    m_buffer += traits_type::to_char_type(c);
    return traits_type::not_eof(c);
}

Тогда используется следующее:

template<typename StreamT>
inline void FixStdStream(DWORD handleId, bool isInput, StreamT& stream)
{
    if (::GetFileType(::GetStdHandle(handleId)) == FILE_TYPE_CHAR)
    {
        stream.rdbuf(new ConsoleStreamBufWin32(handleId, isInput));
    }
}

// ...

int main()
{
    FixStdStream(STD_INPUT_HANDLE, true, std::cin);
    FixStdStream(STD_OUTPUT_HANDLE, false, std::cout);
    FixStdStream(STD_ERROR_HANDLE, false, std::cerr);

    // ...

    std::cout << "\xc3\xbc" << std::endl;

    // ...
}

Пропущенные wstring_to_utf8 и utf8_to_wstring могут быть легко реализованы с помощью WideCharToMultiByte и MultiByteToWideChar функций WinAPI.

1 голос
/ 04 ноября 2009

Oi. Поздравляем с поиском способа изменить кодовую страницу консоли изнутри вашей программы. Я не знал об этом звонке, мне всегда приходилось использовать chcp.

Я предполагаю, что локаль C ++ по умолчанию становится вовлеченной. По умолчанию он использует кодовую страницу, предоставляемую GetThreadLocale (), для определения кодировки текста, не относящегося к wstring. Обычно это по умолчанию CP1252. Вы можете попробовать использовать SetThreadLocale (), чтобы добраться до UTF-8 (если он даже это делает, не может вызвать), в надежде, что по умолчанию std :: locale будет обрабатывать кодировку UTF-8.

0 голосов
/ 19 июня 2013

Пришло время закрыть это сейчас. Стефан Т. Лававей говорит , что поведение "по замыслу", хотя я не могу следовать этому объяснению.

Насколько я знаю: консоль Windows XP в кодовой странице UTF-8 не работает с C ++ iostreams.

Windows XP сейчас выходит из моды, как и VS 2008. Мне было бы интересно узнать, сохраняется ли проблема в более новых системах Windows.

В Windows 7 эффект, вероятно, связан с тем, как C ++ выводит символы в поток. Как видно из ответа на Правильная печать символов utf8 в консоли Windows , вывод UTF-8 завершается неудачно с помощью C stdio при печати одного байта за другим, например putc('\xc3'); putc('\xbc');. Возможно, это то, что здесь делают потоки C ++.

...