是否可以在Windows控制台中使用Boost和STL打印UTF-8字符串?

时间:2016-03-25 18:18:02

标签: c++ boost utf-8 stl

我正在尝试使用cout输出UTF-8编码的字符串但没有成功。我想在我的程序中使用Boost.Locale。我找到了一些关于Windows控制台的信息。例如,本文http://www.boost.org/doc/libs/1_60_0/libs/locale/doc/html/running_examples_under_windows.html说我应该将输出控制台代码页设置为65001,并使用BOM以UTF-8编码保存我的所有源代码。所以,这是我的简单例子:

#include <windows.h>
#include <boost/locale.hpp>

using namespace std;
using namespace boost::locale;

int wmain(int argc, const wchar_t* argv[])
{
    //system("chcp 65001 > nul"); // It's the same as SetConsoleOutputCP(CP_UTF8)
    SetConsoleOutputCP(CP_UTF8);

    locale::global(generator().generate(""));

    static const char* utf8_string = u8"♣☻▼►♀♂☼";

    cout << "cout: " << utf8_string << endl;
    printf("printf: %s\n", utf8_string);

    return 0;
}

我使用Visual Studio 2015进行编译,它在控制台中生成以下输出:

cout: ���������������������
printf: ♣☻▼►♀♂☼

为什么printf做得好,而cout没有? Boost的语言环境生成器可以帮忙吗?或者我应该使用其他东西在流模式下以类似cout的方式在控制台中打印UTF-8文本?

2 个答案:

答案 0 :(得分:1)

看起来std::cout在这里太聪明了:它试图将你的utf8编码的字符串解释为ascii,并找到21个非ascii字符,它输出为未映射的字符。 AFAIK Windows C ++控制台驱动程序,坚持将每个字符从一个窄字符串映射到屏幕上的位置,并且不支持多字节字符集。

在幕后发生的事情:

utf8_string是以下的char数组(只需查看Unicode表并执行utf8转换):

utf8_string = { '0xe2', '0x99', '0xa3', '0xe2', '0x98', '0xbb', '0xe2', '0x96',
    '0xbc', '0xe2', '0x96', '0xba', '0xe2', '0x99', '0x80', '0xe2', '0x99',
    '0x82', '0xe2', '0x98', '0xbc', '\0' };

即21个字符,其中没有一个在ascii范围0-0x7f。

在另一侧,printf只输出字节而不进行任何转换,从而得到正确的输出。

对不起,即使经过多次搜索,我也找不到一种简单的方法,可以使用std::cout这样的窄流在Windows控制台上正确显示UTF8输出。

但是你应该注意到你的代码无法将助推器语言环境灌输到cout

答案 1 :(得分:0)

关键问题是,经过漫长而痛苦的冒险之后cout << "some string"的实施会为每个角色调用WriteFile

如果您想调试它,请在CRT源的_write文件中的write.c函数内设置断点,写一些内容到cout,您将看到所有故事。< / p>

所以我们可以重写你的代码

static const char* utf8_string = u8"♣☻▼►♀♂☼";
cout << utf8_string << endl;

与等效(和更快!)一:

static const char* utf8_string = u8"♣☻▼►♀♂☼";
const size_t utf8_string_len = strlen(utf8_string);
DWORD written = 0;
for(size_t i = 0; i < utf8_string_len; ++i)
    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), utf8_string + i, 1, &written, NULL);

输出:���������������������

WriteFile的单个调用替换循环,UTF-8控制台变得非常棒:

static const char* utf8_string = u8"♣☻▼►♀♂☼";
const size_t utf8_string_len = strlen(utf8_string);
DWORD written = 0;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), utf8_string, utf8_string_len, &written, NULL);

输出:♣☻▼►♀♂☼

我在msvc.2013和msvc.net(2003)上对它进行了测试,两者的行为完全相同。

显然,控制台的Windows实现需要调用WriteFile / WriteConsole时的整个字符,并且不能通过单个字节获取UTF-8字符。 :)

我们可以在这做什么?

我的第一个想法是使输出缓冲,就像在文件中一样。这很简单:

static char cout_buff[128];
cout.rdbuf()->pubsetbuf(cout_buff, sizeof(cout_buff));
cout << utf8_string << endl; // works
cout << utf8_string << endl; // do nothing

输出:♣☻▼►♀♂☼(只有一次,我稍后解释)

第一个问题是控制台输出变得延迟,它等到行尾或缓冲区溢出。

第二个问题 - 它不起作用。

为什么呢?第一次缓冲区刷新后(首先<< endlcout切换到错误状态(badbit设置)。这是因为WriteFile通常会返回*lpNumberOfBytesWritten个写入的字节数,但对于UTF-8控制台,它会返回写入的字符数(问题描述) here)。 CRT检测到写入和写入请求的字节数不同,并停止写入“失败”流。

我们可以做些什么?

好吧,我想我们可以实现自己的std::basic_streambuf来编写控制台正确的方法,但这并不容易,我没有时间。如果有人想要,我会很高兴。

另一个决定是(a)使用std::wcoutwchar_t字符串,(b)使用WriteFile / WriteConsole。有时可以接受这些解决方案。

在Microsoft版本的C ++中使用UTF-8控制台非常糟糕。