繁体   English   中英

UNICODE, UTF-8 和 Windows 一团糟

[英]UNICODE, UTF-8 and Windows mess

我正在尝试在 Windows 中实现文本支持,目的是稍后也迁移到 Linux 平台。 以统一的方式支持国际语言是最理想的,但考虑到所讨论的两个平台,这似乎并不容易实现。 我花了相当多的时间阅读 UNICODE、UTF-8(和其他编码)、widechars 等,这是我到目前为止所了解的内容:

作为标准,UNICODE 描述了可映射的字符集及其出现的顺序。 我将此称为“什么”:UNICODE 指定可用的内容。

UTF-8(和其他编码)指定如何:每个字符将如何以二进制格式表示。

现在,在windows上,他们本来选择了UCS-2编码,但是不能满足要求,所以他们有UTF-16,必要时也是多字符。

所以这里是困境:

  1. Windows 内部只支持 UTF-16,所以如果你想支持国际字符,你必须转换成它们的 widechar 版本以相应地使用操作系统调用。 似乎不支持使用多字节 UTF-8 字符串调用 CreateFileA() 之类的东西,并让它看起来正确。 这样对吗?
  2. 在 C 中,有一些多字节支持函数(_mbscat、_mbscpy 等),但是在 windows 中,这些函数的字符类型被定义为 unsigned char*。 鉴于 _mbs 系列函数不是一个完整的集合(例如,没有 _mbstol 可以将多字节字符串转换为长字符串),您不得不使用一些 char* 版本的运行时函数,由于这些函数之间的有符号/无符号类型差异,这会导致编译器问题。 有没有人甚至使用那些? 你只是做了一大堆铸造来解决错误吗?
  3. 在 C++ 中,std::string 有迭代器,但这些迭代器基于 char_type,而不是代码点。 因此,如果我在 std::string::iterator 上执行 ++,我会得到下一个 char_type,而不是下一个代码点。 同样,如果您调用 std::string::operator[],您将获得对 char_type 的引用,它很可能不是一个完整的代码点。 那么如何通过代码点迭代 std::string 呢? (C 具有 _mbsinc() 函数)。

只做UTF-8

在每个plaftorm中都有很多UTF-8的支持库,也有一些是多平台的。 Win32中的UTF-16 API是有限的,并且与您已经注意到的不一致,因此最好将所有内容保存在UTF-8中并在最后时刻转换为UTF-16。 Windows API还有一些方便的UTF-8包装。

此外,在应用程序级文档中,UTF-8作为标准越来越被接受。 每个文本处理应用程序都接受UTF-8,或者最坏的情况下将其显示为“带有一些dingbats的ASCII”,而只有少数应用程序支持UTF-16文档,而那些不支持UTF-16文档的应用程序则显示为“批量和批量”空白!“

  1. 正确。 您将为Windows API调用将UTF-8转换为UTF-16。

  2. 大多数情况下,您将使用UTF-8的常规字符串函数 - strlenstrcpy (ick), snprintfstrtol 它们可以与UTF-8字符一起使用。 要么使用char *作为UTF-8,要么你必须投射一切。

    请注意,像_mbstowcs这样的下划线版本不是标准版本,它们通常以没有下划线的名称命名,例如mbstowcs

  3. 很难想出你真正想在Unicode字符串上使用operator[]例子,我的建议是远离它。 同样,迭代字符串的用法令人惊讶:

    • 如果要解析字符串(例如,字符串是C或JavaScript代码,也许您需要语法高亮显示),那么您可以逐字节地完成大部分工作并忽略多字节方面。

    • 如果您正在进行搜索,您也将逐字节地执行此操作(但请记住首先进行规范化)。

    • 如果您正在寻找单词中断或字形集合边界,您将需要使用像ICU这样的库。 算法并不简单。

    • 最后,您始终可以将一大块文本转换为UTF-32并以此方式使用它。 我认为如果您正在实施任何Unicode算法(如整理或分解),这是最常用的选项。

    请参阅: C ++迭代或将UTF-8字符串拆分为符号数组?

  1. Windows内部仅执行UTF-16,因此如果要支持国际字符,则必须转换为其widechar版本以相应地使用OS调用。 似乎没有任何支持使用多字节UTF-8字符串调用类似CreateFileA()的东西并让它看起来正确。 它是否正确?

对,那是正确的。 *A函数变体根据当前活动的代码页(在美国和西欧的大多数计算机上是Windows-1252,但通常可以是其他代码页)来解释字符串参数,并将它们转换为UTF-16。 有一个UTF-8代码页,但是AFAIK没有办法以编程方式设置活动代码页(有GetACP来获取活动代码页,但没有相应的SetACP )。

  1. 在C中,有一些多字节支持函数(_mbscat,_mbscpy等),但是,在Windows上,字符类型被定义为这些函数的unsigned char *。 鉴于_mbs系列函数不是一个完整的集合(例如,没有_mbstol将多字节字符串转换为long,例如),您将被迫使用运行时函数的一些char *版本,由于这些函数之间的有符号/无符号类型差异,这会导致编译器问题。 有没有人甚至使用那些? 你只是做了一大堆铸造来解决错误吗?

根据我的经验, mbs*系列功能几乎从未使用过。 用的例外mbstowcsmbsrtowcs ,和mbsinit ,这些功能都没有标准C.

  1. 在C ++中,std :: string有迭代器,但它们基于char_type而不是代码点。 因此,如果我在std :: string :: iterator上执行++,我会得到下一个char_type,而不是下一个代码点。 类似地,如果你调用std :: string :: operator [],你会得到一个char_type的引用,它很有可能不是一个完整的代码点。 那么如何通过代码点迭代std :: string呢? (C具有_mbsinc()函数)。

我认为mbrtowc(3)将是解码多字节字符串的单个代码点的最佳选择。

总的来说,我认为跨平台Unicode兼容性的最佳策略是使用单字节字符在内部执行UTF-8中的所有操作。 当您需要调用Windows API函数时,将其转换为UTF-16并始终调用*W变体。 大多数非Windows平台已经使用UTF-8,因此可以快速使用它们。

在 Windows 中,可以调用WideCharToMultiByteMultiByteToWideChar在 UTF-8 字符串和 UTF-16 字符串(Windows 中为 wstring)之间进行转换。 因为 Windows API 不使用 UTF-8,所以每当你调用任何支持 Unicode 的 Windows API function 时,你必须将字符串转换为 wstring8 in 848TF18 中的 848-18 版本(Windows U8-65 版本) 当你从 Windows 得到 output 时,你必须将 UTF-16 转换回 UTF-8。Linux 内部使用 UTF-8,所以你不需要这样的转换。 要使您的代码可移植到 Linux,请坚持使用 UTF-8 并提供以下内容以进行转换:

#if (UNDERLYING_OS==OS_WINDOWS)
 
using os_string = std::wstring;

std::string utf8_string_from_os_string(const os_string &os_str)
{
    size_t length = os_str.size();
    int size_needed = WideCharToMultiByte(CP_UTF8, 0, os_str, length, NULL, 0, NULL, NULL);
    std::string strTo(size_needed, 0);
    WideCharToMultiByte(CP_UTF8, 0, os_str, length, &strTo[0], size_needed, NULL, NULL);
    return strTo;
}

os_string utf8_string_to_os_string(const std::string &str)
{
    size_t length = os_str.size();
    int size_needed = MultiByteToWideChar(CP_UTF8, 0, str, length, NULL, 0);
    os_string wstrTo(size_needed, 0);
    MultiByteToWideChar(CP_UTF8, 0, str, length, &wstrTo[0], size_needed);
    return wstrTo;
}

#else

// Other operating system uses UTF-8 directly and such conversion is
// not required
using os_string = std::string;
#define utf8_string_from_os_string(str)    str
#define utf8_string_to_os_string(str)    str

#endif

要迭代 utf8 字符串,您需要两个基本函数:一个计算 utf8 字符的字节数,另一个确定该字节是否是 utf8 字符序列的前导字节。 下面的代码提供了一种非常有效的测试方法:

inline size_t utf8CharBytes(char leading_ch)
{
    return (leading_ch & 0x80)==0 ? 1 : clz(~(uint32_t(uint8_t(leading_ch))<<24));
}

inline bool isUtf8LeadingByte(char ch)
{
    return  (ch & 0xC0) != 0x80;
}

使用这些函数,在utf8字符串上实现自己的迭代器应该不难,一个是forwarding iterator,一个是backward iterator。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM