std :: cout可以在Windows上使用UTF-8吗?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了std :: cout可以在Windows上使用UTF-8吗?相关的知识,希望对你有一定的参考价值。

我想让std::cout打印一个UTF-8文字。这对于gcc来说似乎是一件容易的事,但对于Windows来说这是一个非常困难的任务。

我正在努力工作的代码是:

std::cout << "Ελληνικά Русский 你好";

环境:

  • Windows 10,Visual Studio 2015
  • 默认编码:1251
  • 控制台编码:866
  • 源编码:带BOM的UTF-8

要求:

  • 不能对代码行本身进行任何更改
  • 完整的Unicode范围支持
  • 可以在main()的开头添加一些设置代码

我尝试过的:

  • #pragma execution_character_set("utf-8")
  • SetConsoleCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8);
  • 将控制台字体设置为系统范围内的Lucida Console
  • Use Unicode character set项目物业
  • 来自this博客的安装代码

没有任何帮助,没有StackOverflow答案解决了这个问题。

编辑

要使Unicode部分工作,请执行以下操作:

  • 请在下面的列表中致电initStreams()
  • 在项目设置中打开Use Unicode Character Set
  • 添加/utf-8选项

不工作:

  • wprintf
  • cin / wcin
  • 中国文字

initStreams()实施:

#include <cassert>         // assert
#include <codecvt>          // std::codecvt_utf8 (C++11)
#include <stdexcept>        // std::exception
#include <streambuf>        // std::basic_streambuf
#include <iostream>         // std::cout, std::endl
#include <locale>           // std::locale
#include <memory>           // std::unique_ptr (C++11)

#undef  UNICODE
#define UNICODE
#undef  STRICT
#define STRING
#include <windows.h>    // MultiByteToWideChar

class OutputForwarderBuffer : public std::basic_streambuf<char>
{
public:
    using Base = std::basic_streambuf<char>;
    using Traits = Base::traits_type;
    using StreamBuffer = std::basic_streambuf<char>;
    using WideStreamBuffer = std::basic_streambuf<wchar_t>;
    using Base::int_type;
    using Base::char_type;

    OutputForwarderBuffer(
        StreamBuffer& existingBuffer,
        WideStreamBuffer* pWideStreamBuffer
    )
        : Base(existingBuffer)
        , pWideStreamBuffer_(pWideStreamBuffer)
    {
    }

    OutputForwarderBuffer(OutputForwarderBuffer const&) = delete;
    void operator=(OutputForwarderBuffer const&) = delete;

protected:
    std::streamsize xsputn(char const* s, std::streamsize n) override
    {
        if (n == 0) { return 0; }

        int const sourceSize = static_cast<int>(n);
        int const destinationSize = MultiByteToWideChar(CP_UTF8, 0, s, sourceSize, nullptr, 0);
        wideCharBuffer_.resize(static_cast<size_t>(sourceSize));

        int const nWideCharacters = MultiByteToWideChar(CP_UTF8, 0, s, sourceSize, &wideCharBuffer_[0], destinationSize);
        assert(nWideCharacters > 0 && nWideCharacters == destinationSize);

        return pWideStreamBuffer_->sputn(&wideCharBuffer_[0], destinationSize);
    }

    int_type overflow(int_type c) override
    {
        bool const cIsEOF = Traits::eq_int_type(c, Traits::eof());
        int_type const failureValue = Traits::eof();
        int_type const successValue = (cIsEOF ? Traits::not_eof(c) : c);

        if (!cIsEOF) {
            char_type const ch = Traits::to_char_type(c);
            std::streamsize const nCharactersWritten = xsputn(&ch, 1);

            return (nCharactersWritten == 1 ? successValue : failureValue);
        }
        return successValue;
    }

private:
    WideStreamBuffer* pWideStreamBuffer_;
    std::wstring wideCharBuffer_;
};

void setUtf8Conversion(std::basic_ios<wchar_t>& stream)
{
    stream.imbue(std::locale(std::locale::empty(), new std::codecvt_utf8_utf16<wchar_t>()));
}

bool isConsole(HANDLE streamHandle)
{
    DWORD consoleMode;
    return !!GetConsoleMode(streamHandle, &consoleMode);
}

bool isConsole(DWORD stdStreamId)
{
    return isConsole(GetStdHandle(stdStreamId));
}

void initStreams()
{
    SetConsoleCP(CP_UTF8);
    SetConsoleOutputCP(CP_UTF8);

    setUtf8Conversion(std::wcout);
    setUtf8Conversion(std::wcerr);
    setUtf8Conversion(std::wclog);

    static OutputForwarderBuffer coutBuffer(*std::cout.rdbuf(), std::wcout.rdbuf());
    static OutputForwarderBuffer cerrBuffer(*std::cerr.rdbuf(), std::wcerr.rdbuf());
    static OutputForwarderBuffer clogBuffer(*std::clog.rdbuf(), std::wclog.rdbuf());

    std::cout.rdbuf(&coutBuffer);
    std::cerr.rdbuf(&cerrBuffer);
    std::clog.rdbuf(&clogBuffer);
}
答案

这是我要做的:

  1. 确保您的源文件是utf-8编码并具有正确的内容(在另一个编辑器中打开它们,检查字形和文件编码)
  2. 从等式中删除控制台 - 将输出重定向到文件并使用支持utf-8的编辑器检查其内容(就像源代码一样)
  3. 使用/ utf-8 cmdline选项与MSVC2015 + - 这将强制编译器将所有源文件视为utf-8编码一次,并且存储在结果二进制文件中的字符串文字将是utf-8编码。
  4. 从等式中删除iostreams(不能等到这个库死了,tbh) - 使用cstdio
  5. 在这一点输出应该工作(它对我来说)
  6. 让控制台输出工作 - 使用SetConsoleOutputCP(CP_UTF8)并使其使用支持Unicode平面的TrueType字体(我怀疑中文字符在控制台中工作,您需要在系统中安装支持相关Unicode平面的字体和您的控制台应配置为使用它)
  7. 不确定控制台输入(从来没有处理过),但我怀疑SetConsoleCP(CP_UTF8)应该使它适用于非宽I / o
  8. 放弃使用宽I / O(wcout / etc)的想法 - 你为什么要这样做呢?使用utf-8编码的char const *,Unicode工作得很好
  9. 一旦你到达这个阶段 - 时间来处理iostreams(如果你坚持使用它)。我现在无视wcin / wcout。如果他们还没有工作 - 尝试使用utf-8语言环境提供相关的cin / cout。
  10. http://utf8everywhere.org/提出的想法是仅在进行Windows API调用时才转换为UCS-2。这使得您的OutputForwarderBuffer变得不必要。
  11. 我猜(如果你真的坚持)现在你可以尝试让广泛的iostreams工作。祝你好运,我想你将不得不重新配置控制台(这将打破非宽I / O)或以某种方式让你的wcout / wcin即时执行UCS2到UTF8转换(并且只有当它连接到控制台时) 。

编辑:从Windows 10开始,您还需要:

setvbuf(stderr, NULL, _IOFBF, 1024);    // on Windows 10+ we need buffering or console will get 1 byte at a time (screwing up utf-8 encoding)
setvbuf(stdout, NULL, _IOFBF, 1024);

不幸的是,这也意味着如果在下次冲洗之前完全填充缓冲区,仍有可能搞砸输出。正确的解决方案 - 在每个字符串发送到输出后手动刷新(endlfflush())(假设每个字符串小于1024)。如果只有MS支持行缓冲......

以上是关于std :: cout可以在Windows上使用UTF-8吗?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 printf() 可以在内核中工作,但使用 std::cout 不能?

为啥 printf() 可以在内核中工作,但使用 std::cout 不能?

为啥 std::cout 输出溢出?

多次调用std :: cout会使子进程挂起

QT 如何printf和std::cout的内容显示在程序界面上

使用 std::addressof(std::cout) 代替 &std::cout 有啥风险吗?