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,输出变为1ü
,意思是
ü
可以使用fputs
及其UTF-8 编码C3 BC
编写
std::cout
无论出于何种原因都不起作用
failbit
在尝试写入字符后正在设置流
Windows 7: 使用 Consolas 的输出是 ��0ü
。更有趣的是。可能写入了正确的字节(至少在将输出重定向到文件时)并且流状态正常,但两个字节被写入为单独的字符。
我试图在“Microsoft Connect”上提出这个问题(参见here), 但 MS 并没有太大帮助。你不妨看看here 以前有人问过类似的问题。
你能重现这个问题吗?
我做错了什么? std::cout
和 fputs
不应该相同吗
效果?
已解决:(有点)按照 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 并发送到控制台。目前未发现任何问题。
另请注意,此解决方案不需要任何代码页修改,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 函数实现。
【讨论】:
这是一个有用的想法。对于输出,我最终得到了一个派生自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 输出的主要内容,如果未能解决你的问题,请参考以下文章