Windows 控制台上的 UTF-8 输出

Posted

技术标签:

【中文标题】Windows 控制台上的 UTF-8 输出【英文标题】:UTF-8 output on Windows console 【发布时间】:2010-12-12 05:48:37 【问题描述】:

以下代码在我的机器上显示了意外行为(在 Windows XP 上使用 Visual C++ 2008 SP1 和在 Windows 7 上使用 VS 2012 进行了测试):

#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”上提出这个问题(参见here), 但 MS 并没有太大帮助。你不妨看看here 以前有人问过类似的问题。

你能重现这个问题吗?

我做错了什么? std::coutfputs 不应该相同吗 效果?

已解决:(有点)按照 mike.dld 的想法,我实现了一个 std::stringbuf,在 sync() 中进行从 UTF-8 到 Windows-1252 的转换,并替换了 std::cout 的流缓冲区使用此转换器(请参阅我对 mike.dld 答案的评论)。

【问题讨论】:

我以前在使用 c++ iostreams 时遇到过问题。有很多隐藏的讨厌会导致问题。这不值得回答,但是当 iostreams 给您带来麻烦时,请使用 c 的 stdio,我以前曾多次遇到过类似的问题。 是的,使用 iostreams 比 stdio 更复杂,甚至有 full-length text books 关于这个。但是 iostreams 为您提供了很大的灵活性,我很乐意使用它。 这不是 Windows 控制台的问题吗?我记得它无论如何都不知道 unicode,造成了很多这样的问题...... 如您所见,我可以在 Windows 控制台中输出 UTF-8 编码的字符串(通过 fputs),我可以使用 @987654340 输入 UTF-8 编码的文件@ 命令(在完成 chcp 65001 之后)。因此我认为它可以处理这种编码...... 【参考方案1】:

我知道这个问题已经很老了,但如果有人仍然感兴趣,下面是我的解决方案。我已经实现了一个非常简单的 std::streambuf 后代,然后在程序执行的最开始将它传递给每个标准流。

这允许您在程序中的任何地方使用 UTF-8。在输入时,数据以 Unicode 格式从控制台获取,然后转换并以 UTF-8 格式返回给您。在输出时完成相反的操作,以 UTF-8 格式从您那里获取数据,将其转换为 Unicode 并发送到控制台。目前未发现任何问题。

另请注意,此解决方案不需要任何代码页修改,SetConsoleCPSetConsoleOutputCPchcp 或其他内容。

那是流缓冲区:

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_utf8utf8_to_wstring 可以很容易地用WideCharToMultiByteMultiByteToWideChar WinAPI 函数实现。

【讨论】:

这是一个有用的想法。对于输出,我最终得到了一个派生自std::stringbuf 的类(因此我不必自己进行缓冲),并且只是实现了sync() 进行转换。我的sync() 不是在代码中硬连线输出接收器,而是将转换后的字符串插入到流原始streambuf 中。【参考方案2】:

喂。恭喜您找到了从程序内部更改控制台代码页的方法。我不知道那个调用,我总是必须使用 chcp。

我猜 C++ 默认语言环境正在参与其中。默认情况下,它将使用 GetThreadLocale() 提供的代码页来确定非 wstring 内容的文本编码。这通常默认为 CP1252。您可以尝试使用 SetThreadLocale() 来获取 UTF-8(如果它甚至这样做,不记得了),希望 std::locale 默认为可以处理您的 UTF-8 编码的东西。

【讨论】:

绝对不是解决方案,而是我以前从未想过的事情。我会在几天后回到工作岗位时尝试一下(在家里我正在使用 Linux……)。 我又看了一遍,但是SetThreadLocale没有处理编码,或者看不懂msdn.microsoft.com/en-us/library/dd374051(VS.85).aspx的文档。我尝试了一点 std::cout.imbue 但无济于事。这个问题仍未解决...【参考方案3】:

现在是时候关闭它了。 Stephan T. Lavavej says 行为是“设计使然”,尽管我无法理解这种解释。

我目前的知识是:UTF-8 代码页中的 Windows XP 控制台不适用于 C++ iostream。

Windows XP 现在已经过时了,VS 2008 也是如此。我很想知道这个问题在较新的 Windows 系统上是否仍然存在。

在 Windows 7 上,效果可能是由于 C++ 流输出字符的方式。从对Properly print utf8 characters in windows console 的回答中可以看出,UTF-8 输出在像putc('\xc3'); putc('\xbc'); 一样一个接一个地打印一个字节时使用 C stdio 失败。也许这就是 C++ 流在这里所做的。

【讨论】:

它存在 :( 我正在尝试在 ***.com/questions/23584160/… 中找到解决方法,欢迎您 :)

以上是关于Windows 控制台上的 UTF-8 输出的主要内容,如果未能解决你的问题,请参考以下文章

node.js在控制台上获得批量输出

如何在 Windows 控制台上的 C 程序中用希腊语进行 I/O

java控制台输出乱码

如何去掉控制台上输出的这些日志

java io怎么读取文件并输出到控制台上?

如何在控制台上的同一位置写入输出?