【问题标题】:Issue when converting utf16 wide std::wstring to utf8 narrow std::string for rare characters将 utf16 宽 std::wstring 转换为 utf8 窄 std::string 时出现问题
【发布时间】:2020-03-03 00:44:11
【问题描述】:

为什么某些 utf16 编码的宽字符串在转换为 utf8 编码的窄字符串时会转换为使用此常见转换函数转换时似乎不正确的十六进制值?

std::string convert_string(const std::wstring& str)
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
    return conv.to_bytes(str);
}

你好。我在 Windows 上有一个 C++ 应用程序,它在命令行上接受一些用户输入。我正在使用宽字符主入口点将输入作为 utf16 字符串获取,我正在使用上述函数将其转换为 utf8 窄字符串。

这个功能可以在网上的很多地方找到,几乎在所有情况下都可以使用。但是,我发现了一些似乎没有按预期工作的示例。

例如,如果我输入一个表情符号“????”作为字符串文字(在我的 utf8 编码的 cpp 文件中)并将其写入磁盘,文件 (FILE-1) 包含以下数据(它们是此处指定的正确 utf8 十六进制值https://www.fileformat.info/info/unicode/char/1f922/index.htm):

    0xF0 0x9F 0xA4 0xA2

但是,如果我在命令行上将表情符号传递给我的应用程序,并使用上面的转换函数将其转换为 utf8 字符串,然后将其写入磁盘,则文件 (FILE-2) 包含不同的原始字节:

    0xED 0xA0 0xBE 0xED 0xB4 0xA2

如果您复制并粘贴十六进制值(至少在 notepad++ 中),第二个文件似乎表明转换产生了错误的输出,但它会产生正确的表情符号。 WinMerge 也认为这两个文件是相同的。

所以总结一下,我真的很想知道以下几点:

  1. 在上面的示例中,看起来不正确的转换十六进制值如何正确映射到正确的 utf8 字符
  2. 为什么转换函数将某些字符转换为这种形式,而几乎所有其他字符都生成预期的原始字节
  3. 作为奖励,我想知道是否可以修改转换函数以阻止它以这种形式输出这些稀有字符

我应该注意到我已经有一个使用 WinAPI 调用的变通函数,但是只使用标准库调用是梦想:)

std::string convert_string(const std::wstring& wstr)
{
    if(wstr.empty())
        return std::string();

    int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
    std::string strTo(size_needed, 0);
    WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
    return strTo;
}

【问题讨论】:

    标签: c++ string unicode utf-8 character-encoding


    【解决方案1】:

    问题在于std::wstring_convert&lt;std::codecvt_utf8&lt;wchar_t&gt;&gt; 从 UCS-2 转换,不是从 UTF-16。 BMP 内部的字符 (U+0000..U+FFFF) 在 UCS-2 和 UTF-16 中具有相同的编码,因此可以工作,但 BMP 之外的字符 (U+FFFF..U+10FFFF),例如作为您的表情符号,UCS-2 中根本不存在。这意味着转换不理解字符并产生不正确的 UTF-8 字节(从技术上讲,它会将 UTF-16 代理对的每一半转换为单独的 UTF-8 字符)。

    您需要改用std::wstring_convert&lt;std::codecvt_utf8_utf16&lt;wchar_t&gt;&gt;

    【讨论】:

    • 非常感谢!这确实是问题所在。我没有意识到我最初使用的方面是在 utf8 和 USC-2 之间转换。
    【解决方案2】:

    这里已经有一个经过验证的答案。但是为了记录,这里有一些额外的信息。

    nauseated face emojiencoding 于 2016 年在 Unicode 中引入。它是 4 个 utf-8 字节 (0xF0 0x9F 0xA4 0xA2) 或 2 个 utf-16 字 (0xD83E 0xDD22)。

    0xED 0xA0 0xBE 0xED 0xB4 0xA2 令人惊讶的编码实际上对应于 UCS surrogate pair

    所以基本上,您的第一个编码是直接 utf8。第二种编码是 UCS-2 编码的 utf8 编码,对应于所需字符的 utf-16 编码。

    正如公认的答案正确指出的那样,std::codecvt_utf8&lt;wchar_t&gt; 是罪魁祸首,因为它是关于 UCS-2 而不是 UTF-16。

    现在在标准库中发现这种过时的编码是相当令人惊讶的,但我怀疑这仍然是微软在标准委员会中游说的回忆,该标准委员会可以追溯到旧的 Windows support for unicode 和 UCS-2。

    【讨论】:

      猜你喜欢
      • 2012-12-03
      • 1970-01-01
      • 1970-01-01
      • 2021-08-21
      • 2011-04-06
      • 1970-01-01
      • 2011-11-01
      • 2019-08-27
      • 2011-01-03
      相关资源
      最近更新 更多