📄 windows核心编程1-8章.txt
字号:
当我为本书的第一版编写示例程序时,我编写的原始程序只能编译为ANSI程序。后来,当我开始撰写本章的内容时,我想我应该鼓励使用Unicode,并且想要创建一些示例程序,以便展示你可以非常容易地编写既可以用Unicode也可以用ANSI来编译的程序。因此我决定最好是将本书中的所有示例程序进行转换,使它们都能够用Unicode和ANSI进行编译。
我用了大约4个小时将所有程序进行了转换。考虑到我以前从来没有这方面的转换经验,这个速度是相当不错了。
2.9.1 Windows字符串函数
Windows也提供了一组用于对Unicode字符串进行操作的函数,下面这个表对它们进行了描述。
函数 描述
lstrcat 将一个字符串置于另一个字符串的结尾处
lstrcmp 对两个字符串进行区分大小写的比较
lstrcmpi 对两个字符串进行不区分大小写的比较
lstrcpy 将一个字符串拷贝到内存中的另一个位置
lstrlen 返回字符串的长度(按字符数来计量)
这些函数是作为宏来实现的,这些宏既可以调用函数的Unicode版本,也可以调用函数的ANSI版本,这要根据编译源代码模块时是否已经定义了UNICODE而定。例如,如果没有定义UNICODE,lstrcat函数将扩展为lstrcatA。如果定义了UNICODE,lstrcat将扩展为lstrcatW。
有两个字符串函数,即lstrcmp和lstrcmpi,它们的行为特性与等价C运行期函数是不同的。C运行期函数strcmp、strcmpi、wcscmp和wcscmpi只是对字符串中的代码点的值进行比较,这就是说,这些函数将忽略实际字符的含义,只是将第一个字符串中的每个字符的数值与第二个字符串中的字符的数值进行比较。而Windows函数lstrcmp和lstrcmpi是作为对Windows函数CompareString的调用来实现的。
见原书P31的程序(1)
该函数对两个Unicode字符串进行比较。CompareString的第一个参数用于设定语言ID(LCID),这是个32位值,用于标识一种特定的语言。CompareString使用这个LCID来比较这两个字符串,方法是对照一种特定的语言来查看它们的字符的含义。这种操作方法比C运行期函数简单地进行数值比较更有意义。
当lstrcmp函数系列中的任何一个函数调用CompareString时,该函数便将调用Windows的GetThreadString函数的结果作为第一个参数来传递:
见原书P31的程序(2)
每次创建一个线程时,它就被赋予一种语言。函数将返回该线程的当前语言设置。
CompareString的第二个参数用于标识一些标志,这些标志用来修改该函数比较两个字符串时所用的方法。下面这个表显示了可以使用的标志。
标志 含义
NORM_IGNORECASE 忽略字母的大小写
NORM_IGNOREKANATYPE 不区分平假名与片假名字符
NORM_IGNORENONSPACE 忽略无间隔字符
NORM_IGNORESYMBOLS 忽略符号
NORM_IGNOREWIDTH 不区分单字节字符与作为双字节字符的同一
个字符
SORT_STRINGSORT 将标点符号作为普通符号来处理
当lstrcmp调用CompareString时,它传递0作为fdwStyle的参数。但是,当lstrcmpi调用CompareString时,它就传递NORM_IGNORECASE。CompareString的其余4个参数用于设定两个字符串和它们各自的长度。如果你为cch1参数传递-1,那么该函数将认为pString1字符串是以0结尾,并计算该字符串的长度。对于pString2字符串来说,参数cch2的作用也是一样。
其他C运行期函数没有为Unicode字符串的操作提供很好的支持。例如,tolower和toupper函数无法正确地转换带有重音符号的字符。为了弥补C运行期库中的这些不足,你必须调用下面这些Windows函数,以便转换Unicode字符串的大小写字母。这些函数也可以正确地用于ANSI字符串。
头两个函数
见原书P32的(1)
和
见原书P32的(2)
既可以转换单个字符,也可以转换以0结尾的整个字符串。若要转换整个字符串,你只需要传递字符串的地址即可。若要转换单个字符,你必须象下面这样传递各个字符:
见原书P32的程序(3)
将单个字符转换成一个PTSTR,便可调用该函数,将一个值传递给它,在这个值中,较低的16位包含了该字符,较高的16位包含0。当该函数看到较高位是0时,该函数就知道你想要转换单个字符,而不是整个字符串。返回的值是个32位值,较低的16位中是已经转换的字符。
下面两个函数与前面两个函数很相似,差别在于它们用于转换缓存中包含的字符(该缓存不必以0结尾):
见原书P32的程序(4)
其他的C运行期函数,如isalpha,islower和isupper,返回一个值,指明某个字符是字母字符,小写字母还是大写字母。Windows API提供了一些函数,也能返回这些信息,但是Windows函数也要考虑用户在控制面板中指定的语言:
见原书P33的程序(1)
printf函数家族是我们要介绍的最后一组C运行期函数。如果你在定义了_UNICODE的情况下编译你的源代码模块,那么printf函数家族便希望所有字符和字符串参数代表Unicode字符和字符串。但是,如果在没有定义_UNICODE的情况下编译你的源代码模块,printf函数家族便希望传递给它的所有字符和字符串都是ANSI字符和字符串。
Microsoft公司已经给C运行期的printf函数家族增加了一些特殊的域类型。其中有些域类型尚未被ANSI C采用。新类型使你能够很容易地对ANSI和Unicode字符和字符串进行混合和匹配。操作系统的wsprintf函数也得到了增强。下面是一些例子(请注意大写S和小写s的使用):
见原书P33的程序(2)
2. 9.2 资源
当资源编译器对你的所有资源进行编译时,输出文件是个资源的二进制文件。你的资源(字符串表、对话框模板和菜单等)中的字符串值总是写作Unicode字符串。在Windows 98和Windows 2000下,如果你的应用程序没有定义UNICODE宏,那么系统就会进行内部转换。
例如,如果你在编译源代码模块时没有定义UNICODE,调用LoadString实际上就是调用LoadStringA函数。这时LoadStringA就从你的资源中读取字符串,并将该字符串转换成ANSI字符串。ANSI形式的字符串将从该函数返回给你的应用程序。
2. 9.3 确定文本是ANSI文本还是Unicode文本
到现在为止,Unicode文本文件仍然非常少。实际上,Microsoft公司自己的大多数产品并没有配备任何Unicode文本文件。但是我预计将来这种情况是会改变的(尽管这需要一个很长的过程)。当然,Windows 2000的Notepad(记事本)应用程序允许你既能打开Unicode文件,也能打开ANSI文件,并且可以创建这些文件。图2-2显示了Notepad的File Save As(文件另存为)对话框。请注意你可以用不同的方法来保存文本文件。
图2-2 Windows 2000 Notepad的File Save As对话框
对于许多用来打开文本文件和处理这些文件的应用程序(如编译器)来说,打开一个文件后,应用程序就能方便地确定该文本文件是包含ANSI字符还是Unicode字符。IsTextUnicode函数能够帮助进行这种区分:
见原书P34的程序
文本文件存在的问题是,它们的内容没有严格和明确的规则,因此很难确定该文件是包含ANSI字符还是Unicode字符。IsTextUnicode使用一系列统计方法和定性方法,以便猜测缓存的内容。由于这不是一种确切的科学方法,因此IsTextUnicode有可能返回不正确的结果。
第一个参数pvBuffer用于标识你要测试的缓存的地址。该数据是个无效指针,因为你不知道你拥有的是ANSI字符数组还是Unicode字符数组。
第二个参数cb用于设定pvBuffer指向的字节数。同样,由于你不知道缓存中放的是什么,因此cb是个字节数,而不是字符数。请注意,你不必设定缓存的整个长度。当然,IsTextUnicode能够测试的字节越多,你得到的结果越准确。
第三个参数pResult是个整数的地址,你必须在调用IsTextUnicode之前对它进行初始化。你对该整数进行初始化后,就可以指明你要IsTextUnicode执行哪些测试。你也可以为该参数传递NULL,在这种情况下,IsTextUnicode将执行它能够进行的所有测试。(详细说明请参见Platform SDK资料。)
如果IsTextUnicode认为缓存包含Unicode文本,便返回TRUE,否则返回FALSE。确实是这样,尽管Microsoft将该函数的原型规定为返回DWORD,但是它实际上返回一个布尔值。如果在pResult参数指向的整数中必须进行特定的测试,该函数就会在返回之前设定整数中的信息位,以反映每个测试的结果。
Windows
98 在Windows 98下,IsTextUnicode函数没有有用的实现代码,它
只是返回FALSE。调用GetLastError函数将返回ERROR_CALL_NOT
_IMPLEMENTD。
2. 9.4 在Unicode与ANSI之间转换字符串
Windows函数MultiByteToWideChar用于将多字节字符串转换成宽字符串。下面显示了MultiByteToWideChar函数。
见原书P36的程序(1)
uCodePage参数用于标识一个与多字节字符串相关的代码页号。dwFlags参数用于设定另一个控件,它可以用重音符号之类的区分标记来影响字符。这些标志通常并不使用,而在dwFlags参数中则传递0。pMultiByteStr参数用于设定要转换的字符串,cchMultiByte参数用于指明该字符串的长度(按字节计算)。如果你为cchMultiByte参数传递-1,那么该函数用于确定源字符串的长度。
转换后产生的Unicode版本字符串将被写入内存中的缓存,其地址由pWideCharStr参数指定。你必须在cchWideChar参数中设定该缓存的最大值(以字符为计量单位)。如果你调用MultiByteToWideChar,给cchWideChar参数传递0,那么该参数将不执行字符串的转换,而是返回为使转换取得成功所需要的缓存的值。一般来说,你可以通过下列步骤将多字节字符串转换成Unicode等价字符串:
1. 调用MultiByteToWideChar函数,为pWideCharStr参数传递NULL,为cchWideChar
参数传递0。
2. 分配足够的内存块,用于存放转换后的Unicode字符串。该内存块的大小值由前面的对MultByteToWideChar的调用返回。
3. 再次调用MultiByteToWideChar,这次将缓存的地址作为pWideCharStr参数来传递,并传递第一次调用MultiByteToWideChar时返回的缓存大小值,作为cchWidechar参数。
4. 使用转换后的字符串。
5. 释放Unicode字符串占用的内存块。
函数WideCharToMultiByte将宽字符串转换成等价的多字节字符串,如下面所示:
见原书P37的程序(1)
该函数与MultiBiteToWideChar函数相似。同样,uCodePage参数用于标识与新转换的字符串相关的代码页。DwFlags则设定用于转换的其他控件。这些标志能够作用于带有区分符号的字符和系统不能转换的字符。通常你不需要为字符串的转换而拥有这种程度的控制手段,你将为dwFlags参数传递0
pWideCharStr参数用于设定要转换的字符串的内存地址,cchWideChar参数用于指明该字符串的长度(用字符数来计量)。如果你为cchWideChar参数传递-1,那么该函数用于确定源字符串的长度。
转换产生的多字节版本的字符串被写入由pMultiByteStr参数指明的缓存。你必须在cchMultiByte参数中设定该缓存的最大值(用字节来计量)。如果传递0作为WideCharToMultiByte函数的cchMultiByte参数,那么该函数将返回目标缓存需要的大小值。你通常可以使用将多字节字符串转换成宽字节字符串时介绍的一系列类似的事件,将宽字节字符串转换成多字节字符串。
你会发现,WideCharToMultiByte函数接受的参数比MultiByteToWideChar函数要多2个,即pDegaultchar和pfUsedDefaultchar。只有当WideCharToMultiByte函数遇到一个宽字节字符,而该字符在uCodePage参数标识的代码页中并没有它的表示法时,WideCharToMultiByte函数才使用这两个参数。如果宽字节字符不能被转换,该函数便使用pDefaultChar参数指向的字符。如果该参数是NULL,这是大多数情况下的参数值,那么该函数使用系统的默认字符。该默认字符通常是个问号。这对于文件名来说是危险的,因为问号是个通配符。
pfUsedDefaultChar参数指向一个布尔变量,如果宽字符串中至少有一个字符不能转换成等价多字节字符,那么函数就将该变量置为TRUE。如果所有字符均被成功地转换,那么该函数就将该变量置为FALSE。当函数返回以便检查宽字节字符串是否被成功地转换后,你可以测试该变量。同样,你通常为该测试传递NULL。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -