Windows 控制台上的 UTF-8 输出

问题描述 投票:0回答:5

以下代码在我的计算机上显示了意外行为(在 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ü
(翻译为Codepage 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
,在
sync()
中进行从UTF-8到Windows-1252的转换,并用此转换器替换了
std::cout
的streambuf(请参阅我的评论)关于 mike.dld 的回答)。

c++ visual-studio-2008 utf-8 windows-xp console
5个回答
6
投票

我知道这个问题已经很老了,但如果有人仍然感兴趣,下面是我的解决方案。我实现了一个非常简单的 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
投票

哎哟。恭喜您找到了一种从程序内部更改控制台代码页的方法。我不知道那个电话,我总是不得不使用 chcp。

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


1
投票

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

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

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

在 Windows 7 上,这种效果可能是由于 C++ 流输出字符的方式造成的。正如对“在 Windows 控制台中正确打印 utf8 字符”的回答中所见,当像 putc('\xc3'); putc('\xbc'); 这样一个接一个地打印字节时,C stdio 的 UTF-8 输出会失败。也许这就是 C++ 流在这里所做的事情。

    


0
投票
mike.dld

在这个问题中的回答,并添加 printf

UTF-8
字符串的支持。
正如

mkluwe

在他的回答中提到的,默认情况下,printf函数会一个字节一个字节地输出到控制台,而控制台无法正确处理单个字节。我的方法很简单,我使用

snprintf
函数将整个内容打印到内部字符串缓冲区,然后将缓冲区转储到
std::cout
这是完整的测试代码:

#include <iostream> #include <locale> #include <windows.h> #include <cstdlib> using namespace std; // https://stackoverflow.com/questions/4358870/convert-wstring-to-string-encoded-in-utf-8 #include <codecvt> #include <string> // convert UTF-8 string to wstring std::wstring utf8_to_wstring (const std::string& str) { std::wstring_convert<std::codecvt_utf8<wchar_t>> myconv; return myconv.from_bytes(str); } // convert wstring to UTF-8 string std::string wstring_to_utf8 (const std::wstring& str) { std::wstring_convert<std::codecvt_utf8<wchar_t>> myconv; return myconv.to_bytes(str); } // https://stackoverflow.com/questions/1660492/utf-8-output-on-windows-console // mike.dld's answer 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)); } } // some code are from this blog // https://blog.csdn.net/witton/article/details/108087135 #define printf(fmt, ...) __fprint(stdout, fmt, ##__VA_ARGS__ ) int __vfprint(FILE *fp, const char *fmt, va_list va) { // https://stackoverflow.com/questions/7315936/which-of-sprintf-snprintf-is-more-secure size_t nbytes = snprintf(NULL, 0, fmt, va) + 1; /* +1 for the '\0' */ char *str = (char*)malloc(nbytes); snprintf(str, nbytes, fmt, va); std::cout << str; free(str); return nbytes; } int __fprint(FILE *fp, const char *fmt, ...) { va_list va; va_start(va, fmt); int n = __vfprint(fp, fmt, va); va_end(va); return n; } 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; printf("\xc3\xbc"); // ... return 0; }

源代码以
UTF-8

格式保存,并在Msys2的GCC下构建并在Windows 7 64位下运行。这是结果

ü
ü



0
投票

#include <filesystem> #include <fstream> #include <iostream> #include <string> #ifdef _WIN32 #include <windows.h> #endif int main() { #ifdef _WIN32 SetConsoleOutputCP(CP_UTF8); #endif std::string content = "Hello Word 😃"; std::filesystem::path current_dir = std::filesystem::current_path(); std::filesystem::path file_path = current_dir / u8"Hello 😃.txt"; std::ofstream file(file_path, std::ios::binary); if (!file.is_open()) return 1; std::cout << "Filepath : " << content << std::endl; file.write(content.c_str(), content.size()); file.close(); return 0; }

在 Windows 中测试:

clang main.cpp -o main.exe -std=c++20

在适用于 Linux 的 Windows 子系统中进行了测试:

g++ main.cpp -o test.elf --std=c++20

© www.soinside.com 2019 - 2024. All rights reserved.