我可以依靠 TCHAR 的定义对我正在使用的字符集做出正确的假设吗?

Can I rely on TCHAR's definition to make the correct assumptions about the charset I'm using?

我正在重新访问一个旧的 MFC 爱好项目,并试图使它对 Unicode 更友好。因此,我一直将 char 的所有实例替换为 TCHAR,将 strlen() 替换为 _tcslen() 等等。

但是,I just discovered这些类型和函数实际上与所有 语言字符集不兼容。例如,日语字符显然由三个字节表示,而不是一个:

I would like to know the number of characters in a TCHAR array or TCHAR*. Unfortunately, every length function I can find (_tcslen(), even wstring::length()) seems to be returning the number of BYTES, not characters...a Japanese character counts as three and a Roman character counts as one.

但是,this Microsoft documentation page 建议使用 TCHARs 将在所有情况下确保您的安全:

To be safe in all cases, you should use the following convention when dealing with TCHARs:

TCHAR tchBuffer[24];
GetWindowText( hWnd, tchBuffer, sizeof(tchBuffer)/sizeof(TCHAR));

In doing this, your code will be safe when compiled as either MBCS or UNICODE.

这是真的吗?或者多字节字符集(例如日语字符集)会导致 UB 吗? MTIA :-)

如评论中所述,使用 wchar_t 会产生更好的结果。

MFC 是在通常使用 char 的时代设计的,多字节字符集只能对一种语言进行编码(例如 Shift-JIS 是日语字符的编码)。

从那时起,wchar_t 接管了一个可用的集合(在 Windows 上 wchar_t 是一个无符号的短整型,并且编码为 UTF-16)。

我的建议是直接转换为wchar_t,忽略tchar中间位置。

UTF-16 确实使用多个 int16 值对某些字符进行编码

In doing this, your code will be safe when compiled as either MBCS or UNICODE.

这不是真的,无论您使用的是哪种基本字符类型。

在 arbitrary/buffer-size 偏移量处截断未知字符串永远不会安全。 UTF-16(Windows 平台上的 wchar_t)具有 surrogate pairs and even if you switch to UTF-32 you would still have issues with decomposed combining characters, digraphs 和颜色修饰符。

使用 GetStringType 获取有关特定字符的信息 and/or 使用 CharNext 遍历字符串以找到合适的停止点。

您确实需要决定您的应用程序目标是什么 api。

如果如您标记的那样,它是基于 MFC 的,则您应该使用 MFC 的 C++ 字符串表示形式 CString 及其在 Windows 平台上处理 Ansi 和 Unicode 的规则。

同样,如果您主要针对 windows API 编写,那么您定义的类型是:字符文字的 CHAR、TCHAR 和 WCHAR,以及 *STR、*TSTR 和 *WSTR用于字符串缓冲区。

如果您首先编写的是 C++ 应用程序——恰好在 windows 上实现——那么更喜欢 std: 类型,例如 std:string 和 std:wstring

最后,如果你想通过 C 表示来表示字符串,那么 char*,wchar_t*,如果你想能够在 unicode 和 ansi 之间动态切换,那么 _tchar* 及其.

中定义的助手类型

在 Ansi 和 Unicode 之间切换 在所有类型中,当您在 Ansi 和 Unicode 之间切换编译器时,CString、TCHAR、*TSTR 和 _tchar 将在 8 位和 16 位类型之间切换。

但实际上 - 将应用程序编译为 Ansi: * 效率低下,因为 Windows API 已经成为 nativley unicode 一段时间了,因此 Ansi 应用程序中具有字符串参数的所有 api 调用都被强制转换所有输入参数和出路的out参数。 * 容易丢失数据,因为 Ansi 应用程序(几乎)永远不会同时处理来自两个不同代码页的字符。

无论如何Ansi/MBCS可以安全地编码什么 Windows API 定义了一个 "Ansi Code Page"。我不知道为什么它叫 Ansi,但你可以通过调用 GetACP 来获取当前版本。如果设置为,例如CP_LATIN1,然后尝试加载、处理、输入或处理日文、韩文等字符将失败。这是区域设置控制面板中系统范围的默认设置,因此通常您应该为本地用户设置正确的代码页。

如果您正在使用 c-runtime 函数,那么您需要调用 setlocale 以确保 知道您使用的是什么编码。我不确定 std::string 是否使用 c 语言环境,或者是否有这个想法的 std:: 抽象。关键是,要知道你主要使用哪个字符串抽象,并使用它,所以你不必通过调用所有不同的可能的本地/代码页 apis 来填充你的代码,只是因为一些血腥的?的或块再次以字符串形式弹出。

手头:Utf8 另一方面,该行业的其他人已经朝另一个方向发展,Linux、MacOs 和相应的大多数跨平台库使用 Utf8 编码处理 unicode 字符。它对所有可能的 unicode 字符进行编码,而不会混淆语言环境或代码页或任何废话。而且所有这些都具有非常跨平台的友好性 "char*"。 因此,如果编写跨平台代码对您很重要,那么您将不会使用 wchar_t 或任何宽字符类型。 Windows 10 最终将 Utf8 添加为可能的 Ansi 代码页但是:它是用户必须选择加入的系统设置,因此您的应用程序无法声明或依赖它的启用。我不知道是否可以简单地将它设置为当前线程代码页,我也不知道是否有任何 c-runtime 兼容/利用它来提供无缝的 "closer to posix" 体验你可以期待字符串的工作。

当然,这里需要注意的是 "characters" 现在可以用 1 到 6 个字节的长度进行编码。

字节长度与字符数 不确定你想要什么。您通常不希望 *strlen 之类的函数 return 字符数,因为您将(通常)使用它们的结果来分配内存缓冲区。然而,它们应该 return 计数不是以字节为单位,而是以您正在处理的字符的自然分配单元为单位。即 wcslen("hello") 应该 return 5,无论 wchar_t 的宽度如何,它可以是 2 或 4 个字节。

wchar_t wchar_t 是一个可怕的类型,因为 c/c++ 标准没有定义它的宽度。一些编译器以 2 字节为单位,其他编译器以 4 字节为单位。作为一个 2 字节的单元,它的宽度仅足以存储来自 unicodes "BMP" 或基本多语言平面的字符,但有些字符不能存储在单个 UCS2 / UTF-16 字符中。如果您想 100% 安全,那么您必须使用 char16_t、char32_t 或您特别需要的任何内容。 wchar_t 不是安全选项。

总而言之,情况非常可恶:

  • 你不能在任何地方都使用 plain-old-char 并且依赖于 utf-8 作为合理的默认值,因为 windows 是 Utf-16 本机并且使用 8 位字符集效率非常低,
  • 而且您永远无法保证能够使用 utf-8,因此您很可能会随机受到有损编码的影响。
  • 您不能随处使用 wchar_t,因为它在不同平台上的大小不同。
  • 如果您可以访问稳定的 Utf-16:- posix 平台使用带有 utf8 的普通旧 char* 缓冲区来处理所有导致这些平台上的性能问题的问题,您仍然必须处理理论上的 multi-unit 字符。
  • 使用 TCHAR / _tchar 类型并利用 Visual Studios 的 Unicode 编译器 / 多字节字符集开关是无法忍受的,因为它会给您的应用程序增加很多额外的噪音,并且对跨平台可移植性没有真正帮助,因为所有_t***函数只是ms的一部分c-runtime.