【问题标题】:Reading UTF-8 characters from console从控制台读取 UTF-8 字符
【发布时间】:2018-06-18 23:23:26
【问题描述】:

我正在尝试从控制台为我的 c++ 应用程序读取 UTF-8 编码的波兰字符。 我确定控制台使用此代码页(已签入属性)。 我已经尝试过的:

  • 使用 cin - 而不是 "zażółć" 我读的是 "za\0\0\0\0"
  • 使用 wcin - 而不是“zażółć” - 结果与使用 cin 相同
  • 使用 scanf - 而不是 'zażółć\0' 我读的是 'za\0\0\0\0\0'
  • 使用 wscanf - 结果与使用 scanf 相同
  • 使用 getchar 逐一读取字符 - 结果与使用 scanf 相同

在 main 函数的开头我有以下几行:

setlocale(LC_ALL, "PL_pl.UTF-8");
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);

我将非常乐意提供帮助。

【问题讨论】:

  • 你试试ReadConsoleW 吗?
  • 如果你能让它工作我会很惊讶,windows真的不使用utf-8,它更喜欢utf-16。
  • 首先检查SetConsoleCP的返回值
  • 代码页 65001 (UTF-8) 不适用于读取非 ASCII(7 位)字符,即使使用 Windows 10 中的新控制台也是如此。在旧版本中,整个调用仅返回读取的 0 个字符如果字符串中有 1 个非 ASCII 字符。在 Windows 10 中,它用 NUL 代替非 ASCII 字符。该错误在控制台主机进程中。 conhost.exe,它使用暂存缓冲区调用WideCharToMultiByte,其大小适用于单字节或双字节代码页(取决于系统区域设置),但不是可变编码,例如 UTF-8。
  • 只需运行 WCHAR cc[256]; ULONG n; ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), cc, RTL_NUMBER_OF(cc), &n, 0); MessageBoxW(0,cc,0,0); 即可查看您阅读的内容

标签: c++ windows visual-c++ utf-8


【解决方案1】:

这是我用于 UTF-8 支持的技巧。结果是多字节字符串,然后可以在其他地方使用:

#include <cstdio>
#include <windows.h>
#define MAX_INPUT_LENGTH 255

int main()
{

    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);

    wchar_t wstr[MAX_INPUT_LENGTH];
    char mb_str[MAX_INPUT_LENGTH * 3 + 1];

    unsigned long read;
    void *con = GetStdHandle(STD_INPUT_HANDLE);

    ReadConsole(con, wstr, MAX_INPUT_LENGTH, &read, NULL);

    int size = WideCharToMultiByte(CP_UTF8, 0, wstr, read, mb_str, sizeof(mb_str), NULL, NULL);
    mb_str[size] = 0;

    std::printf("ENTERED: %s\n", mb_str);

    return 0;
}

应该是这样的:

附:非常感谢 Remy Lebeau 指出了一些缺陷!

【讨论】:

  • 另外,如果你要调用WideCharToMultiByte() 来获得mb_str 所需的大小,你应该动态分配mb_str 到那个大小,否则当mb_str 时你有缓冲区溢出的风险是一个静态数组(* 2 在从 UTF-16 到 UTF-8 时是不够的,你需要 * 3 甚至 * 4 代替)。如果确实使用静态数组,则无需调用两次WideCharToMultiByte(),只需传递mb_str 的最大大小即可:int size = WideCharToMultiByte(CP_UTF8, 0, wstr, read, mb_str, sizeof(mb_str), NULL, NULL); mb_str[size] = 0;
  • 您可以缩减到MAX_INPUT_LENGTH * 3。 BMP 代码在 UTF-8 中最多为 3 个字节。除此之外,UTF-16 使用 2 个代理代码,与 UTF-8 一样为 4 个字节,在mb_str 中过度分配为 6 个字节。
  • 删除 SetConsoleOutputCP(CP_UTF8)SetConsoleCP(CP_UTF8)。在 Windows 8 之前,将输出代码页设置为 UTF-8 被破坏(WriteFileWriteConsoleA 返回为非 ASCII 字符写入的错误字节数),并且将输入代码页设置为 UTF-8 在所有情况下都被严重破坏Windows 版本(ReadFileReadConsoleA 将非 ASCII 字符替换为 NUL 或返回读取的 0 字节)。确保 UNICODE 被定义为 ReadConsole 真的是 ReadConsoleW,或者显式调用 ReadConsoleW
  • 您应该使用WriteConsoleW 将UTF-16 写入控制台,或者将CRT 模式设置为_O_U16TEXT 并使用宽字符C API。 WriteFileWriteConsoleA 在 Windows 7 中被 UTF-8 破坏。例如,使用带有 C fwrite 的缓冲 FILE 将看到写入的字节数错误,并尝试在几个字节中写入“剩余”字节用 UTF-8 写的都是乱码。并且控制台的 ReadFileReadConsoleA 在 UTF-8 中完全被破坏,即使在 Windows 10 中也是如此,因此将控制台输入代码页设置为 UTF-8 绝对没有意义(零增益 - 所有痛苦)。
  • 您好,我只是想说我喜欢您的测试字符串。
【解决方案2】:

虽然您已经接受了答案,但这里有一个更便携的版本,它更接近标准库。不幸的是,这是我发现许多广泛使用的实现不支持标准中所谓的东西的一个领域。例如,应该有一种打印多字节字符串的标准方法(理论上可能是像 shift-JIS 这样不寻常的东西,但实际上在每个现代操作系统上都是 UTF-8),但它实际上并不能移植。微软的运行时库在这方面尤其差,但我也发现了 libc++ 的 bug。

/* Boilerplate feature-test macros: */
#if _WIN32 || _WIN64
#  define _WIN32_WINNT  0x0A00 // _WIN32_WINNT_WIN10
#  define NTDDI_VERSION 0x0A000002 // NTDDI_WIN10_RS1
#  include <sdkddkver.h>
#else
#  define _XOPEN_SOURCE     700
#  define _POSIX_C_SOURCE   200809L
#endif

#include <iostream>
#include <locale>
#include <locale.h>
#include <stdlib.h>
#include <string>

#ifndef MS_STDLIB_BUGS // Allow overriding the autodetection.
/* The Microsoft C and C++ runtime libraries that ship with Visual Studio, as
 * of 2017, have a bug that neither stdio, iostreams or wide iostreams can
 * handle Unicode input or output.  Windows needs some non-standard magic to
 * work around that.  This includes programs compiled with MinGW and Clang
 * for the win32 and win64 targets.
 *
 * NOTE TO USERS OF TDM-GCC: This code is known to break on tdm-gcc 4.9.2. As
 * a workaround, "-D MS_STDLIB_BUGS=0" will at least get it to compile, but
 * Unicode output will still not work.
 */
#  if ( _MSC_VER || __MINGW32__ || __MSVCRT__ )
    /* This code is being compiled either on MS Visual C++, or MinGW, or
     * clang++ in compatibility mode for either, or is being linked to the
     * msvcrt (Microsoft Visual C RunTime) library.
     */
#    define MS_STDLIB_BUGS 1
#  else
#    define MS_STDLIB_BUGS 0
#  endif
#endif

#if MS_STDLIB_BUGS
#  include <io.h>
#  include <fcntl.h>
#endif

using std::endl;
using std::istream;
using std::wcin;
using std::wcout;

void init_locale(void)
// Does magic so that wcout can work.
{
#if MS_STDLIB_BUGS
  // Windows needs a little non-standard magic.
  constexpr char cp_utf16le[] = ".1200";
  setlocale( LC_ALL, cp_utf16le );
  _setmode( _fileno(stdout), _O_WTEXT );
  _setmode( _fileno(stdin), _O_WTEXT );
#else
  // The correct locale name may vary by OS, e.g., "en_US.utf8".
  constexpr char locale_name[] = "";
  setlocale( LC_ALL, locale_name );
  std::locale::global(std::locale(locale_name));
  wcout.imbue(std::locale());
  wcin.imbue(std::locale());
#endif
}

int main(void)
{
  init_locale();

  static constexpr size_t bufsize = 1024;
  std::wstring input;
  input.reserve(bufsize);

  while ( wcin >> input )
    wcout << input << endl;

  return EXIT_SUCCESS;
}

无论其初始语言环境或代码页如何,它都会从控制台读取宽字符输入。如果您的意思是输入将是 UTF-8 编码中的字节(例如来自 UTF-8 编码中的重定向文件),而不是控制台输入,那么完成此操作的标准方法应该是从&lt;codecvt&gt;&lt;locale&gt; 中的 UTF-8 到 wchar_t,但实际上 Windows 不支持 Unicode 语言环境,因此您必须读取字节然后手动转换它们。一个更标准的方法是mbstowcs()。我有一些旧代码来为 STL 迭代器进行转换,但标准库中也有转换函数。无论如何,您可能需要这样做,例如,如果您需要以 UTF-8 保存或传输。

some who will recommend 您在内部将所有字符串存储在 UTF-8 中,即使使用基于某种形式的 UTF-16 的 Windows 之类的 API,只有在您进行 API 调用时才转换为另一种编码。我强烈建议您尽可能在外部使用 UTF-8,但我不会走得太远。但是请注意,将字符串存储为 UTF-8 将为您节省大量内存,尤其是在 wchar_t 为 UCS-32 的系统上。你会比我更清楚这通常会为波兰语文本节省多少字节。

【讨论】:

  • UTF-8 和 UTF-16 之间的转换很简单,我自己写了:stackoverflow.com/a/148766/5987。可能比依赖标准库的不一致实现更容易。我也曾经做过一个程序,将字符串内部保存为 UTF-8 只是为了好玩。 UTF-8 的案例在utf8everywhere.org.
  • 是的,我也是。我会添加链接。
  • 不幸的是,对于“zażółć”,它会打印“zaz¢lc”。
  • @J.Łyskawa 这个错误修复适用于 VC 2017,控制台设置为代码页 437、1251 或 65001(UTF-8,大概是你想要的)。它对你有用吗?
  • 截至 2022 年,Windows 10 和 11 理论上支持代码页 65001、".65001" 系列 UTF-8 语言环境,以及用于支持 UTF-8 的 _O_U8TEXT 标志 _setmode。在实践中,它们似乎不起作用。
猜你喜欢
  • 1970-01-01
  • 2015-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-26
  • 2013-11-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多