我怎样才能使Unicode iostream I / O在Windows和Unix的土地上工作?

注意:这是一个问题答案 ,以便logging其他人可能会觉得有用的技术,以便或许能够意识到别人的更好的解决scheme。 随意添加评论或问题作为评论。 也请随时添加额外的答案。 🙂


问题1:

  • 通过stream控制台对Unicode的支持在Windows API级别受到严重限制。 可用于普通桌面应用程序的唯一相关代码页是65001,UTF-8。 然后交互式input在API级失败,甚至非ASCII字符输出失败 – 而C ++标准库实现不提供这个问题的解决方法。
#include <iostream> #include <string> using namespace std; auto main() -> int { wstring username; wcout << L"Hi, what's your name? "; getline( wcin, username ); wcout << "Pleased to meet you, " << username << "!\n"; } 
 H:\ personal \ web \ blog alf on wordpress \ 002 \ code> chcp 65001
有效代码页:65001

 H:\ personal \ web \ blog alf on wordpress \ 002 \ code> g ++ problem.input.cpp -std = c ++ 14

 H:\ personal \ web \ blog alf on wordpress \ 002 \ code> a
嗨, SørenMoskégård
                              ←没有可见的输出。
 H:\ personal \ web \ blog alf on wordpress \ 002 \ code> _

在Windows API级别,解决scheme是在相关标准stream绑定到控制台时使用基于非基于stream的直接控制台I / O。 例如,使用WriteConsole API函数。 作为Visual C ++和MinGW g ++标准库支持的扩展,可以为使用WriteConsole的标准宽stream设置一个模式,也可以使用UTF-8作为外部编码的模式。

而在Unix-land中,只要调用setlocale( LC_ALL, "" )或其更高级别的C ++等价,就足以使宽stream工作。

但是怎样才能透明地自动设置这样的模式,这样,在Windows和Unix-land中,使用宽stream的普通标准C ++代码就可以工作?

注意到,对于在Unix-land程序中使用宽文本的读者而言,这实际上是在Unix-land中使用UTF-8窄文本控制台I / O的可移植代码的先决条件 。 也就是说,在Unix-land和Windows中的宽文本中自动使用UTF-8窄文本的代码变得可能,并且可以build立在W​​indows中对Unicode的支持之上。 但是没有这样的支持,一般情况下是不能移植的。


问题2:

  • 使用宽stream时,默认将输出项转换为wchar_t const*不起作用。
 #include <iostream> using namespace std; struct Byte_string { operator char const* () const { return "Hurray, it works!"; } }; struct Wide_string { operator wchar_t const* () const { return L"Hurray, it works!"; } }; auto main() -> int { wcout << "Byte string pointer: " << Byte_string() << endl; wcout << "Wide string pointer: " << Wide_string() << endl; } 
字节string指针:Hurray,它的工作原理!
宽string指针:0x4ad018

这是我在很久以前报告的标准实施级别的不一致types的缺陷。 我不确定这个状态,它可能已经被遗忘了(我从来没有收到任何关于它的邮件),或者一个修补程序将被应用在C ++ 17中。 无论如何,如何解决这个问题呢?


简而言之,如何制作使用Unicode宽文本控制台I / O的标准C ++代码,在Windows和Unix-land中工作和实用?

修复转换问题:

CPPX / STDLIB / iostreams_conversion_defect.fix.hpp

 #pragma once //---------------------------------------------------------------------------------------- // PROBLEM DESCRIPTION. // // Output of wchar_t const* is only supported via an operator<< template. User-defined // conversions are not considered for template matching. This results in actual argument // with user conversion to wchar_t const*, for a wide stream, being presented as the // pointer value instead of the string. #include <iostream> #ifndef CPPX_NO_IOSTREAM_CONVERSION_FIX namespace std{ template< class Char_traits > inline auto operator<<( basic_ostream<wchar_t, Char_traits>& stream, wchar_t const ch ) -> basic_ostream<wchar_t, Char_traits>& { return operator<< <wchar_t, Char_traits>( stream, ch ); } template< class Char_traits > inline auto operator<<( basic_ostream<wchar_t, Char_traits>& stream, wchar_t const* const s ) -> basic_ostream<wchar_t, Char_traits>& { return operator<< <wchar_t, Char_traits>( stream, s ); } } // namespace std #endif 

在Windows中设置直接I / O模式:

这是Visual C ++和MinGW g ++支持的标准库扩展。

首先,仅仅因为在代码中使用了它, Ptr类型构建器的定义(库提供的类型构建器的主要缺点是普通类型推断不起作用,即在某些情况下仍然需要使用原始操作符表示法):

CPPX / core_language / type_builders.hpp

 ⋮ template< class T > using Ptr = T*; ⋮ 

助手定义,因为它被用在多个文件中:

CPPX / STDLIB / Iostream_mode.hpp

 #pragma once // Mode for a possibly console-attached iostream, such as std::wcout. namespace cppx { enum Iostream_mode: int { unknown, utf_8, direct_io }; } // namespace cppx 

模式设定器(基本功能):

CPPX / STDLIB / IMPL / utf8_mode.for_windows.hpp

 #pragma once // UTF-8 mode for a stream in Windows. #ifndef _WIN32 # error This is a Windows only implementation. #endif #include <cppx/stdlib/Iostream_mode.hpp> #include <stdio.h> // FILE, stdin, stdout, stderr, etc. // Non-standard headers, which are de facto standard in Windows: #include <io.h> // _setmode, _isatty, _fileno etc. #include <fcntl.h> // _O_WTEXT etc. namespace cppx { inline auto set_utf8_mode( const Ptr< FILE > f ) -> Iostream_mode { const int file_number = _fileno( f ); // See docs for error handling. if( file_number == -1 ) { return Iostream_mode::unknown; } const int new_mode = (_isatty( file_number )? _O_WTEXT : _O_U8TEXT); const int previous_mode = _setmode( file_number, new_mode ); return (0?Iostream_mode() : previous_mode == -1? Iostream_mode::unknown : new_mode == _O_WTEXT? Iostream_mode::direct_io : Iostream_mode::utf_8 ); } } // namespace cppx 

CPPX / STDLIB / IMPL / utf8_mode.generic.hpp

 #pragma once #include <stdio.h> // FILE, stdin, stdout, stderr, etc. #include <cppx/core_language/type_builders.hpp> // cppx::Ptr namespace cppx { inline auto set_utf8_mode( const Ptr< FILE > ) -> Iostream_mode { return Iostream_mode::unknown; } } // namespace cppx 

CPPX / STDLIB / utf8_mode.hpp

 #pragma once // UTF-8 mode for a stream. For Unix-land this is a no-op & the locale must be UTF-8. #include <cppx/core_language/type_builders.hpp> // cppx::Ptr #include <cppx/stdlib/Iostream_mode.hpp> namespace cppx { inline auto set_utf8_mode( const Ptr< FILE > ) -> Iostream_mode; } // namespace cppx #ifdef _WIN32 // This also covers 64-bit Windows. # include "impl/utf8_mode.for_windows.hpp" // Using Windows-specific _setmode. #else # include "impl/utf8_mode.generic.hpp" // A do-nothing implementation. #endif 

配置标准流。

除了在Windows中适当地设置直接的控制台I / O模式或UTF-8之外,它还修复了隐式转换缺陷。 (间接)调用setlocale以便在Unix领域中工作在较宽的流; 设置boolalpha只是为了好的措施,作为一个更合理的默认; 并包括所有标准的库头文件,以处理iostreams(我不显示单独的头文件这样做,它是在一定程度上个人喜好多少包括或是否做这样的包含):

CPPX / STDLIB / iostreams.hpp

 #pragma once // Standard iostreams but configured to work, plus, as utility, with boolalpha set. #include <raw_stdlib/iostreams.hpp> // <iostream>, <sstream>, <fstream> etc. for convenience. #include <cppx/core_language/type_builders.hpp> // cppx::Ptr #include <cppx/stdlib/utf8_mode.hpp> // stdin etc., stdlib::set_utf8_mode #include <locale> // std::locale #include <string> // std::string #include <cppx/stdlib/impl/iostreams_conversion_defect.fix.hpp> // Support arg conv. inline auto operator<< ( std::wostream& stream, const std::string& s ) -> std::wostream& { return (stream << s.c_str()); } // The following code's sole purpose is to automatically initialize the streams. namespace cppx { namespace utf8_iostreams { using std::locale; using std::ostream; using std::cin; using std::cout; using std::cerr; using std::clog; using std::wostream; using std::wcin; using std::wcout; using std::wcerr; using std::wclog; using std::boolalpha; namespace detail { using std::wstreambuf; // Based on "Filtering streambufs" code by James Kanze published at // <url: http://gabisoft.free.fr/articles/fltrsbf1.html>. class Correcting_input_buffer : public wstreambuf { private: wstreambuf* provider_; wchar_t buffer_; protected: auto underflow() -> int_type override { if( gptr() < egptr() ) { return *gptr(); } const int_type result = provider_->sbumpc(); if( result == L'\n' ) { // Ad hoc workaround for g++ extra newline undesirable behavior: provider_->pubsync(); } if( traits_type::not_eof( result ) ) { buffer_ = result; setg( &buffer_, &buffer_, &buffer_ + 1 ); } return result ; } public: Correcting_input_buffer( wstreambuf* a_provider ) : provider_( a_provider ) {} }; } // namespace detail class Usage { private: static void init_once() { // In Windows there is no UTF-8 encoding spec for the locale, in Unix-land // it's the default. From Microsoft's documentation: "If you provide a code // page like UTF-7 or UTF-8, setlocale will fail, returning NULL". Still // this call is essential for making the wide streams work correctly in // Unix-land. locale::global( locale( "" ) ); // Effects a `setlocale( LC_ALL, "" )`. for( const Ptr<FILE> c_stream : {stdin, stdout, stderr} ) { const auto new_mode = set_utf8_mode( c_stream ); if( c_stream == stdin && new_mode == Iostream_mode::direct_io ) { static detail::Correcting_input_buffer correcting_buffer( wcin.rdbuf() ); wcin.rdbuf( &correcting_buffer ); } } for( const Ptr<ostream> stream_ptr : {&cout, &cerr, &clog} ) { *stream_ptr << boolalpha; } for( const Ptr<wostream> stream_ptr : {&wcout, &wcerr, &wclog} ) { *stream_ptr << boolalpha; } } public: Usage() { static const bool dummy = (init_once(), true); (void) dummy; } }; namespace detail { const Usage usage; } // namespace detail }} // namespace cppx::utf8_iostreams 

这个问题中的两个示例程序只是通过包含上面的头文件而不是或者除了<iostream>来修复的。 除了它可以在一个单独的翻译单元(除了隐式转换缺陷修复,如果需要的话,它的标题必须以某种方式包含)。 或者,例如,在构建命令中强制include。