UNICODE、UTF-8和Windows混乱

vwkv1x7d  于 2023-01-25  发布在  Windows
关注(0)|答案(4)|浏览(494)

我正在尝试在Windows中实现文本支持,并打算稍后迁移到Linux平台。以统一的方式支持国际语言是理想的,但考虑到这两个平台,这似乎并不容易实现。我花了相当多的时间阅读UNICODE、UTF-8(和其他编码),widechars等,以下是我到目前为止所了解的:
作为标准,UNICODE描述了可Map的字符集以及它们出现的顺序,我将其称为“什么”:UNICODE指定将提供的 * 内容 *。
UTF-8(和其他编码)指定 * 如何 *:每个字符将如何以二进制格式表示。
现在,在windows上,他们最初选择了UCS-2编码,但这无法满足要求,所以他们使用UTF-16,必要时也可以使用多字符编码。
下面是delemma:

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

r6l8ljro1#

只执行UTF-8
每个平台都有很多支持UTF-8的库,也有一些是多个。正如你已经注意到的,Win32中的UTF-16 API是有限的和不一致的,所以最好保持所有的东西都是UTF-8,并在最后一刻转换为UTF-16。也有一些方便的UTF-8 Package 窗口API。
此外,在应用程序级文档中,UTF-8正越来越多地被接受为标准。每个文本处理应用程序要么接受UTF-8,要么最坏的情况是将其显示为“带有一些小字符的ASCII”,而只有少数应用程序支持UTF-16文档,而那些不支持的应用程序则将其显示为“大量的空白!”

vsikbqxv

vsikbqxv2#

1.正确。您将为Windows API调用将UTF-8转换为UTF-16。
1.大多数情况下,你会使用常规的字符串函数来处理UTF-8--strlenstrcpy(ick),snprintfstrtol。它们可以很好地处理UTF-8字符。要么使用char *来处理UTF-8,要么你必须强制转换所有的字符。
请注意,下划线版本(如_mbstowcs)不是标准的,它们通常不使用下划线命名,如mbstowcs

  • 很难给出一个你真正想在Unicode字符串上使用operator[]的例子,我的建议是远离它。同样,迭代字符串的用法也少得惊人:
  • 如果您正在解析一个字符串(例如,字符串是C或JavaScript代码,可能您需要语法高亮),那么您可以逐个字节地完成大部分工作,而忽略多字节方面。
  • 如果您正在执行搜索,也将逐个字节地执行此操作(但请记住首先进行规范化)。
  • 如果你正在寻找单词的中断或者字素簇的边界,你会想要使用像ICU这样的库,算法并不简单。
  • 最后,你总是可以把一大块文本转换成UTF-32,并以这种方式处理它。我认为这是最明智的选择,如果你正在实现任何Unicode算法,如排序或中断。

参见:C++ iterate or split UTF-8 string into array of symbols?

krcsximq

krcsximq3#

  1. Windows内部只支持UTF-16,所以如果你想支持国际字符,你必须转换成他们的widechar版本,以使用相应的操作系统调用。似乎没有任何支持调用像CreateFileA()这样的多字节UTF-8字符串,并让它看起来正确。这是正确的吗?
    是的,这是正确的。*A函数变体根据当前活动的代码页解释字符串参数(在美国和西欧的大多数计算机上是Windows-1252,但通常可以是其他代码页)并将其转换为UTF-16。有一个UTF-8代码页,但是,AFAIK没有一种方法可以通过编程方式设置活动代码页(有GetACP可以获取活动代码页,但没有对应的SetACP)。
    1.在C语言中,有一些支持多字节的函数(_mbscat,_mbscpy,etc),但是在windows上,这些函数的字符类型被定义为unsigned char *。(即没有_mbstol来将多字节串转换为长的,例如)您被迫使用运行时函数的某些char * 版本,这会导致编译器的问题,因为那些函数之间有符号/无符号类型的差异。有人甚至使用那些吗?你只是做了一大堆的铸造,以绕过错误?
    根据我的经验,mbs*家族的函数几乎从未使用过,除了mbstowcsmbsrtowcsmbsinit,这些函数都不是标准C。
    1.在C中,std::string有迭代器,但是这些迭代器是基于char_type的,而不是基于码点的。因此,如果我在std::string::迭代器上执行,我得到的是下一个char_type,而不是下一个码点。类似地,如果你调用std::string::operator [],你得到的是一个char_type的引用,它很可能不是一个完整的码点。那么,如何按码点迭代std::string呢?(C有_mbsinc()函数)
    我认为mbrtowc(3)是解码多字节字符串的单个码位的最佳选择。
    总的来说,我认为跨平台Unicode兼容性的最佳策略是在内部使用单字节字符来完成UTF-8中的所有操作。当你需要调用Windows API函数时,将其转换为UTF-16,并始终调用*W变体。大多数非Windows平台已经使用UTF-8,因此使用它们很容易。
zmeyuzjn

zmeyuzjn4#

在Windows中,可以调用WideCharToMultiByteMultiByteToWideChar在UTF-8字符串和UTF-16字符串之间进行转换(Windows中为wstring)。由于Windows API不使用UTF-8,因此无论何时调用任何支持Unicode的Windows API函数,都必须将string转换为wstring(Windows版本的Unicode在UTF-16中)。当你从Windows获得输出时,你必须将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字符串上实现自己的迭代器应该不难,一个是用于前向迭代器,另一个是用于后向迭代器。

相关问题