接受的答案解决了这个问题。但该问题还要求更好地了解 Windows 上所有字符类型之间的差异。
编码
Windows(以及几乎所有其他系统)上的Achar
是单个字节。一个字节通常被解释为无符号值 [0..255] 或有符号值 [-128..127]。(旧的 C++ 标准保证有符号范围仅 [-127..127],但大多数实现给出 [-128..127]。我相信 C++11 保证更大的范围。)
ASCII 是 [0..127] 范围内的值到特定字符的字符映射,因此您可以将 ASCII 字符存储在有符号字节或无符号字节中,因此它将始终适合char
.
但是 ASCII 没有大多数语言所需的所有字符,因此通常通过使用字节中可用的其余值来扩展字符集,以表示某些语言(或语言系列)所需的附加字符。因此,虽然 [0..127] 几乎总是意味着相同的东西,但像 150 这样的值只能在特定编码的上下文中解释。对于单字节字母,这些编码称为代码页。
代码页有所帮助,但它们并没有解决所有问题。您总是必须知道特定文档使用哪个代码页才能正确解释它。此外,您通常无法编写使用不同语言的单个文档。
此外,某些语言的字符数超过 256 个,因此无法一对一地映射char
字符。这导致了多字节字符编码的发展,其中 [0..127] 仍然是 ASCII,但其他一些值是“转义”,这意味着您必须查看一些后续char
的 s 才能确定什么字符你真的有。(最好将多字节视为可变字节,因为有些字符只需要一个字节,而另一些则需要两个或更多。)多字节有效,但编写代码很痛苦。
与此同时,内存变得越来越充裕,因此许多组织聚集在一起创建了 Unicode,其目标是进行值到字符的通用映射(对于“字符”的适当模糊定义)。最初,人们认为所有字符(或至少任何人会使用的所有字符)都适合 16 位值,这很好,因为您不必处理多字节编码——您只需每个字符使用两个字节而不是一个。大约在这个时候,微软决定采用 Unicode 作为 Windows 中文本的内部表示。
WCHAR
所以 Windows 有一个类型叫做WCHAR
,一个两字节的值,代表一个“Unicode”“字符”。我在这里使用引号是因为 Unicode 是从最初的两字节编码演变而来的,所以 Windows 所称的“Unicode”在今天并不是真正的 Unicode——它实际上是一种特殊的 Unicode 编码,称为 UTF-16。并且“字符”在 Unicode 中并不像在 ASCII 中那样简单,因为在某些语言中,字符以有趣的方式组合或以其他方式影响相邻字符。
较新版本的 Windows 在内部将这些 16 位WCHAR
值用于文本,但仍有很多代码是为单字节代码页编写的,甚至还有一些是为多字节编码编写的。那些程序仍然使用char
s 而不是WCHAR
s。并且其中许多程序必须与使用旧版本 Windows 的人一起工作,这些旧版本的 Windows 仍然char
在内部使用 s 以及新版本的使用WCHAR
. 因此,设计了一种使用 C 宏和 typedef 的技术,这样您就可以在大多数情况下以一种方式编写代码,并且 - 在编译时 - 选择让它使用char
或WCHAR
.
TCHAR
为了实现这种灵活性,您可以使用 aTCHAR
作为“文本字符”。在某些头文件(通常是<tchar.h>
)中,TCHAR
将根据编译时环境将类型定义为char
或。WCHAR
Windows 标头采用如下约定:
LPTSTR
是一个指向TCHAR
s 字符串的(长)指针。
LPWSTR
是一个指向WCHAR
s 字符串的(长)指针。
LPSTR
是一个指向char
s 字符串的(长)指针。
(L
for“long”是 16 位时代的遗留物,当时我们有 long、far 和 near 指针。今天这些都已经过时了,但L
前缀往往会保留下来。)
大多数接受和返回字符串的 Windows API 函数实际上都替换为两个版本:A
版本(用于“ANSI”字符)和W
版本(用于宽字符)。(同样,这些历史遗留问题。代码页方案通常被称为 ANSI 代码页,尽管我一直不清楚它们是否真的由 ANSI 标准统治。)
因此,当您像这样调用 Windows API 时:
SetWindowText(hwnd, lptszTitle);
你真正在做的是调用一个扩展为SetWindowTextA
or的预处理器宏SetWindowTextW
。它应该与但是TCHAR
定义一致。也就是说,如果你想要char
s 的字符串,你会得到A
版本,如果你想要WCHAR
s 的字符串,你会得到W
版本。
但由于字符串文字,它有点复杂。如果你这样写:
SetWindowText(hwnd, "Hello World"); // works only in "ANSI" mode
那么只有当你以char
版本为目标时才会编译,因为"Hello World"
它是一个 s 字符串char
,所以它只与SetWindowTextA
版本兼容。如果你想要这个WCHAR
版本,你必须写:
SetWindowText(hwnd, L"Hello World"); // only works in "Unicode" mode
这里的L
意思是你想要宽字符。(theL
实际上代表 long,但它与上面的 long 指针的 long 含义不同。)当编译器看到L
字符串上的前缀时,它知道字符串应该被编码为一系列wchar_t
s 而不是char
s。
(针对 Windows 的编译器使用 2 字节的值wchar_t
,这恰好与 Windows 定义的 a 相同WCHAR
。针对其他系统的编译器通常使用 4 字节的值wchar_t
,这是保存单个 Unicode 代码点所需要的。 )
因此,如果您想要可以以任何一种方式编译的代码,您需要另一个宏来包装字符串文字。有两种可供选择: _T()
和TEXT()
。它们的工作方式完全相同。第一个来自编译器的库,第二个来自操作系统的库。所以你写你的代码是这样的:
SetWindowText(hwnd, TEXT("Hello World")); // compiles in either mode
如果您以char
s 为目标,则该宏是一个只返回常规字符串文字的无操作。如果您以WCHAR
s 为目标,则宏会在L
.
那么你如何告诉编译器你想要定位WCHAR
?您定义UNICODE
和_UNICODE
。前者用于 Windows API,后者用于编译器库。确保你永远不会定义一个没有另一个。