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

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

本文关键字:字符集 假设 TCHAR 依靠 定义 我可以      更新时间:2023-10-16

我正在重温一个旧的MFC爱好项目,并试图使其对Unicode更加友好。因此,我一直在用TCHAR替换所有char实例,strlen()_tcslen()等等。

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

我想知道 TCHAR 数组或 TCHAR* 中的字符数。不幸的是,我能找到的每个长度函数(_tcslen(),甚至 wstring::length()) 似乎都在返回字节数,而不是字符数......日语字符计为三个,罗马字符计为一个。

但是,此Microsoft文档页面建议使用TCHARs 将确保您在所有情况下的安全:

为了在所有情况下都安全起见,在处理 TCHAR 时应使用以下约定:

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

这样,您的代码在编译为 MBCS 或 UNICODE 时将是安全的。

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

您确实需要确定应用程序面向的主要 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 和 Uniocode之间切换 在所有类型中,当您在 Ansi 和 Unicode 之间切换编译器时,CString、TCHAR、*TSTR 和 _tchar 将在 8 位和 16 位类型之间切换。

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

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

如果您使用的是 c 运行时函数,则需要调用setlocale以确保知道您正在使用的编码。我不确定 std::string 是否使用 c 语言环境,或者是否有这个想法的 std:: 抽象。关键是,要知道你主要使用哪个字符串抽象,并使用它,这样你就不必仅仅因为一些血腥的?而调用所有不同的本地/代码页 apis。s 或块再次在字符串中弹出。

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

当然,这里需要注意的是,"字符"现在可以编码为1到6个字节。

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

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

所有人都被告知这种情况完全是可恨的:

  • 您不能在任何地方都使用普通的旧字符,而是依赖utf-8作为合理的默认值,因为Windows是Utf-16本机的,并且使用8位字符集效率非常低,
  • 而且您永远无法保证能够预期 UTF-8,因此您可能会随机接受有损编码。
  • 您不能在不同平台上使用wchar_t,因为它的大小不同。
  • 如果您可以访问稳定的 Utf-16 :- posix 平台使用带有 utf8 的普通旧字符*缓冲区来处理导致这些平台上反向性能问题的所有内容,并且您仍然必须处理理论上的多单元特征。
  • 使用 TCHAR/_tchar 类型并利用 Visual Studios 的 Unicode/Multibyte 字符集编译器开关是无法容忍的,因为它会为您的应用程序添加大量额外的噪音,并且并不能真正帮助跨平台可移植性,因为所有 _t*** 函数都只是 ms c 运行时的一部分。

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

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

从那以后,wchar_t接管了一个可用的集合(在Windows上wchar_t是一个无符号的短,并编码UTF-16)。

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

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

这样做,您的代码在编译为 MBCS 或 UNICODE 时将是安全的。

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

以任意/缓冲区大小的偏移量切碎未知字符串从来都不安全。UTF-16(在Windows平台上wchar_t)具有代理项对,即使您切换到UTF-32,您仍然会遇到分解组合字符,二合字母和颜色修饰符的问题。

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