Windows 编程/Unicode
有关 Unicode 标准的参考,请参见 Unicode。
Unicode 是一个行业标准,其目标是提供一种方法,使各种形式和语言的文本能够被计算机编码使用。最初,文本字符在计算机中使用字节宽数据表示:每个可打印字符(以及许多不可打印字符或“控制”字符)都使用单个字节实现,这允许总共 256 个字符。然而,全球化带来了对计算机能够适应世界各地许多不同字母的需求。
旧代码称为 ASCII 或 EBCDIC,但很明显,这些代码都无法处理来自世界各地的所有不同字符和字母。Unicode 是解决这个问题的方案。Windows NT 使用“宽”16 位字符集实现其许多核心功能,接近 Unicode 标准,尽管它也提供了一系列与标准 ASCII 字符兼容的功能。
UNICODE 字符通常称为“宽字符”、“通用字符”或“T 字符”。本书可能会互换使用这些术语。
在 Unicode 之前,有一种国际化尝试引入了具有变长字符的字符串。一些字符,例如标准 ASCII 字符,将是 1 字节长。其他字符,例如扩展字符集,是 2 字节长。随着 UNICODE 的出现,这类字符格式不再受欢迎,因为它们更难编写,也更难阅读。Windows 仍然维护一些功能来处理变长字符串,但我们不会在这里讨论这些功能。
不幸的是,由于所需的字符数量很快超过了 65,536 个可能的 16 位值,使用宽字符的所有优势都消失了。Windows 实际上使用称为 UTF-16 的方法来存储字符,其中大量字符实际上占用 //两个// 字,这些称为“代理对”。这种发展是在大部分 Windows API 文档编写之后发生的,现在很多都已过时。您永远不应该将字符串数据视为“字符数组”,而应该始终将其视为以 null 结尾的块。例如,始终将整个字符串发送到函数以在屏幕上绘制它,不要尝试绘制每个字符。任何在 LPSTR 后面加上方括号的代码都是错误的。
同时,变长字符字符串在称为 UTF-8 的跨平台标准中强势回归,UTF-8 与 UTF-16 相同,但使用 8 位单元。它的主要优势在于无需两个 API。如果使用 UTF-8,'A' 和 'W' API 将相同,并且由于两者都是可变大小的,因此没有缺点。尽管大多数 Windows 程序员不熟悉 UTF-8,但您可能会看到更多使用非 UNICODE API 的引用。
Win32 API 将其所有需要文本输入的功能分为两类。一些函数带有“A”后缀(表示 ASCII),另一些则带有“W”后缀(表示宽字符或 Unicode)。这些函数使用宏“UNICODE”进行区分。
#ifdef UNICODE #define MessageBox MessageBoxW #else #define MessageBox MessageBoxA #endif
由于这种区分,当您收到编译器错误时,您将在“MessageBoxW”上收到错误,而不是简单地收到“MessageBox”。在这种情况下,编译器没有错误。它只是试图遵循一组复杂的宏。
所有需要字符字符串的 Windows 函数都以这种方式定义。如果要在程序中使用 unicode,则需要在包含 windows.h 文件之前显式定义 UNICODE 宏。
#define UNICODE #include <windows.h>
此外,其他库中的某些函数要求您定义宏 _UNICODE。标准库函数可以通过包含 <tchar.h> 文件来提供 unicode。因此,要在项目中使用 unicode,您需要在项目中进行以下声明。
#define UNICODE #define _UNICODE #include <windows.h> #include <tchar.h>
一些头文件包含如下机制,以便当两个 UNICODE 宏之一被定义时,另一个也会自动被定义。
#ifdef UNICODE #ifndef _UNICODE #define _UNICODE #endif #endif
#ifdef _UNICODE #ifndef UNICODE #define UNICODE #endif #endif
如果要编写一个使用 UNICODE 的库,那么在您的头文件中包含此机制可能会有所帮助,这样其他程序员就不必担心包含这两个宏。通常情况下,只定义两个宏中的一个是一个陷阱,所以要注意!
在 C 中,要创建一个宽字符字符串,您需要在字符串前面加上字母“L”。以下是一个例子
char *asciimessage = "This is an ASCII string."; wchar_t *unicodemessage = L"This is a Wide Unicode string.";
数据类型“TCHAR”定义为,如果未定义 unicode,则为 char 类型,如果定义了 UNICODE(在 tchar.h 中),则为宽类型。为了使字符串在 unicode 和非 unicode 之间可移植,我们可以使用 TEXT() 宏自动将字符串定义为 unicode 或非 unicode
TCHAR *automessage = TEXT("This message can be either ASCII or UNICODE!");
使用 TCHAR 数据类型和 TEXT 宏是使代码在不同环境之间可移植的重要步骤。
此外,TEXT 宏可以写成
TEXT("This is a generic string"); _T("This is also a generic string"); T("This is also a generic string");
所有这三个语句都是等效的。
TEXT 宏通常这样定义
#ifdef UNICODE #define TEXT(t) L##t #define _T(t) L##t #define T(t) L##t #else #define TEXT(t) t #define _T(t) t #define T(t) t #endif
- 参见 Unicode
Unicode 字符 0 到 31(U+0000 到 U+001F)属于 C0 控制和基本拉丁块。它们都是控制字符。这些字符对应于 ASCII 集的前 32 个字符。
代码点 | 十进制等效值 | 名称 | C 转义 |
---|---|---|---|
U+0000 | 0 | 空字符 | |
U+0001 | 1 | 报头开始 | |
U+0002 | 2 | 文本开始 | |
U+0003 | 3 | 文本结束 | |
U+0004 | 4 | 传输结束 | |
U+0005 | 5 | 询问 | |
U+0006 | 6 | 确认 | |
U+0007 | 7 | 响铃 | '\a' |
U+0008 | 8 | 退格 | '\b' |
U+0009 | 9 | 水平制表符 | '\t' |
U+000A | 10 | 换行符 | '\n' |
U+000B | 11 | 垂直制表符 | '\v' |
U+000C | 12 | 换页符 | '\f' |
U+000D | 13 | 回车符 | '\r' |
U+000E | 14 | 换出 | |
U+000F | 15 | 换入 | |
U+0010 | 16 | 数据链路转义 | |
U+0011 | 17 | 设备控制 1 | |
U+0012 | 18 | 设备控制 2 | |
U+0013 | 19 | 设备控制 3 | |
U+0014 | 20 | 设备控制 4 | |
U+0015 | 21 | 否定确认 | |
U+0016 | 22 | 同步空闲 | |
U+0017 | 23 | 传输块结束 | |
U+0018 | 24 | 取消 | |
U+0019 | 25 | 介质结束 | |
U+001A | 26 | 替换 | |
U+001B | 27 | 转义 | |
U+001C | 28 | 文件分隔符 | |
U+001D | 29 | 组分隔符 | |
U+001E | 30 | 记录分隔符 | |
U+001F | 31 | 单元分隔符 |
注意:在 Windows API 中,控制字符的含义与命令行程序中的含义不同!通常,控制字符根本不会被处理,而是显示为不可打印字符,例如 ExtTextOut() 和所有 GDI 函数。对于 USER 函数,例如 DrawText()、MessageBox() 和多行按钮,'\n' 将被处理以将文本分成行。对于菜单,'\a' 和 '\t' 具有特殊含义并被预处理。要响铃,'\a' 永远不会起作用。为此,请使用 Beep() 或 sndPlaySound()。
- 动态链接库 (DLL)