分类:
一、Windows API函数分类:
1,基础服务;2,图形设备接口;3,图形化用户界面;4,通用对话框链接库;
5,通用控件链接库;6,Windows外壳;7,网络服务;
文件处理函数:CloseHandle...
网络处理函数:WNetAddConnection...
消息函数:BroadcastSystemMessage...
打印函数:AbortDoc...
文本和字体函数:AddFontResource...
菜单函数:AppendMenu...
位图,图标和光栅运算函数:BitBlt...
绘图函数:AbortPath...
设备场景函数:CombineRgn...
硬件与系统函数:ActiveKeyboardLayout...
进程和线程函数:CancelWaitableTimer...
控件与消息函数:AdjustWindowRect...
二、字符串相关问题
错误:
缓冲区溢出错误(处理字符串的典型错误)已经成为针对应用程序乃至整个操作系统的各个组件
发起安全问题的媒介
字符核心问题:
本地化的核心问题就是处理不同字符集的问题
多年来,我们一致在将文本字符串编码成一组以0结尾的单字节字符
许多人习以为常,所以我们在调用strlen的时候,是返回“以0结尾的一个ANSI
单字节字符数组"的字符数。
问题:有些语言文字系统,中国字最为典型,它们的字符集就有非常多的符号。
但是一个字节最多能够表示256个符号,所以上面的单字节字符的编码系统就远远不够
解决:
简单来说,就是引入两个字节吧
双字节字符集DBCS(double-byte character set)应运而生
在这个字符集中,一个字符串中的每个字符都是由1或2个字节来组成。
DBCS例子:
如果第一个字符在0x81--0x9F或者是 0xE0--0xFC之间,就必须检查下一个字节,这样
才能完整判断出一个汉字。
纠结:
对于程序员,和DBCS打交道简直噩梦;因为不统一啊,有的是一个字节,有的是两个字节
解决:
放弃和抛弃DBCS,直接研究Unicode字符串
Unicode起源:1988由Apple和Xerox共同建立的一项标准。
1991年,专门由协会来开发和推动Unicode;该协会公司很多;
www.Unicode.org
该组织负责维护Unicode标准;目前是
Unicode® 14.0.0
实践:
在WIndows Vista,每个Unicode字符都是使用UTF-16编码
Unicode VS UTF
UTF:Unicode Transformnation Formnat(Unicode转换/翻译格式,另外一种格式)
UTF-16将每个字符编码为2个字节或者说16bits
为何Windows使用UTF-16?
是因为全球各个地方使用的大部分语言,每个字符很容易用一个16bits值表示出来,这样一来
就比较统一,也比较容易遍历字符串并且计算出长度
但是依然有例外,某些语言超出了16bits的表达能力
补丁:
对于这些超出UTF-16能力的语言,它支持使用代理surrogate,
也就是32bits/4字节来便是一个字符的方式;
在节省空间和简化编码之间,UTF-16(16bits)算是一个比较好的折衷;
其他UTF标准:
UTF-8:
将一些字符编码位为1个字节,将另外一些编码为2个字节,一些编码为3字节
还有一些编码为4字节;真是彻底放飞自己
例如:
在0x0080值以下的字符 压缩为1个字节/8bits,对于英文字母足够;
在0x0080--0x07FF之间的字符 转换为2个字符/16 bits,对于欧洲和中东地区的语言比较合适
在0x0800之上 转换为3个字符/24 bits,适合东亚地区语言
surrogate pair 转换为4个字节/32 bits
它目前是相当流行的编码格式。
但是对于0x8000以上的大量字符进行编码的时候,不如 UTF-16高效
UTF-32:
直接将每个字符都编码为4个字节
这个就是非常统一,程序不用考虑过多;
但是从内存角度,保存到文件或者在网络中传输,很少用到这种合适,在程序内部使用比较多
Unicode 现状:
为阿拉伯语,汉语拼音,西里尔文/俄文,希腊语,希伯来语,日语片假名,朝鲜语,拉丁语字符等
这些字符称之为文字符号(script)定义了码位code point(也就是一个符号在字符集中的位置)
增量:新版本的Unicode都是在现有的文字符号的基础上引入了新的字符,甚至会有新的文字符号
还会包含大量的标点符合,数学符号,技术符号,箭头,装饰标志,读音符号以及其他
0000-0007F 是 ASCII 字符,16bits代码是0300-036F,属于常见的变音符号;
实践:
C语言使用char类型 表示一个8bits ANSI 字符。
所以一个字符串,C编译器会将字符串中的每一个字符转换位8bits char 数据类型组成的数组
Microsoft中C/C++ 编译器定义了一个内建的数据类型 wchar_t
它表示一个UTF-16字符;早期没有内建这个,需要标识位来开启,/Ze:wchar_t;建议始终指定打开
基于内建类型比较好的操作Unicode字符;
未内建之前,有个.c文件定义了一个 typeof unsigned short wchar_t;
区分:
Windows 开发团队希望定义自己的数据类型,区分C语言,在Windows头文件WinNT.h 中定义
typeof char CHAR;
typeof wchar_t WCHAR;
除此以外还有
typeof CHAR *PCHAR
typeof CHAR *PSTR
typeof CONST CHAR *PCSTR
typeof WCHAR *PWCHAR
typeof WCHAR *PWSTR
typeof CONST WCHAR *PCWSTR
前缀 __nullterminated 是一个头部注解header annotation,描述一个类型如何用作函数的参数和返回值;
这个可以让编译器来检查代用函数的时候是否违法头部注解所定义的语义
注:在程序中,使用哪种数据类型并不重要,但是最好保持一致。最好能和程序的文档一致,便于可读性
例如:在windows中编程,那就是用windows自定义的,而不是c语言的数据类型
为了能够使得ANSI或者Unicode字符/串通过编译,WinNT.h定义了一下类型和宏
#ifdef UNICODE
typeof WCHAR TCHAR,*PTCHAR, PTSTR;
typeof CONST WCHAR *PCTSTR;
#define __TEXT(quote) quote
#define __TEXT(quote) L##quote
#else
typeof CHAR TCHAR , * PTCHAR,PTSTR;
typeof CONST CHAR *PCTSTR;
#define __TEXT(quote) quote
#endif
#define TEXT(quote) __TEXT(quote)
使用上述类型和宏,这两种编码方式都可以通过编译
TCHAR c = TEXT(‘A’);
实践:
自从Windows NT开始,所有版本都完全使用Unicode来构件字符串
所有核心函数,创建窗口,显示文本,字符串处理等,都是使用Unicode字符串
所有函数被调用,传入一个ANSI字符串(多个ASNI字符组成),首先就是转换为Unicode
然后将Unicode传给操作系统;
如果需要返回ANSI,也是系统先把Unicode转为ASNI,再将结果传给应用程序,都是后台运算
自然,这些转换都是需要产生时间和内存上的开销
例子:
如果Windows函数使用字符串,一般可能会有两个版本
CreateWindowEx 接受Unicode字符串,另一个接收ANSI字符串
原型如下:
//Unicode 版本 后缀W 代表Wide,字符位数多,宽(也称谓宽字符)
HWND WINAPI CreateWindowExW(
DWORD dwExStyle,
PCWSTR pClassName,
PCWSTR pWindowName,
。。。
)
//ANSI 版本 后缀A就是代表ASNI
HWND WINAPI CreateWindowExA(
DWORD dwExStyle,
PCSTR pClassName
PCSTR pWindowName
...
)
实际使用,只会使用CreateWindowEx;
在WinUser.h中CreateWindowEX实际是一个宏或者说函数
#ifdef UNICODE
#define CreateWindowEx CreateWindowExW
#else
#define CreateWindowEx CreateWindowExA
#endif
编译的时候,主要是看是否定义Unicode
而在Windows Vista中,CreateWindowExA彻底变成了转换层,只是负责分配内存,以便将ANSI转为
Unicode字符串,然后代码调用CreateWindowExW,并且向它传递转换后的Unicode字符串;
函数CreateWindowExW返回后,CreateWindowExA会释放掉内存缓冲区,并且将窗口Handle返回;
所以在缓存区中处理字符串,就会在转换一下为需要的非Unicode;由于系统必须进行这些转换,所以
应用程序会使用更多内存,而且运行速度很慢,所以为了提高速度,需要一开始就使用Unicode
更加注意的是,就是转换函数存在bug,所以尽量避免使用他们
实践:在创建dll 提供别人使用的动态链接库,那么可以考虑提供两个函数:一个ASNI版本
一个Unicode版本;
在ANSI中也是只是分配内存,执行必要的字符串转换,然后调用该函数的Unicode版本
注:
有一些Windows函数的存在就是为了兼容16bit windows程序,因为只支持ANSI字符串
所以一定要避免在新程序中使用这些函数
趋势:目前WIndows开始只提供Unicode版本
比如:ReadDirectoryChangesW,CreateProcessWithLogonW
例子:Microsoft在将16bits windows移植到32bits时候,就明确做出一个决策:
只能接收Unicode字符串,这是明智的
最后,当资源编译器编译完所有资源后,输出文件就是资源的一个二进制形式
资源中所有的字符串值都是Unicode字符串保存的。一旦没有定义,就会内部进行转换,非常耗费
C 语言中针对二者Unicode,ANSI的处理:
分别提供了两套一系列函数分别处理ANSI和Unicode
但是C中ANSI版本的函数更加自力更生
内部不会相互转换,而是完全独立,各自处理各自。内部不会相互调用
例子:strlen就是返回ANSI字符串的长度
wcslen就是返回Unicode字符串的长度
这两个函数的原型都在String.h
#ifdef _UNICODE
#define _tcslen wcslen
#else
#define _tcslen strlen
#endif
解决最开始提出的缓冲区不足引发的字符串错误
任何修改字符串的函数都存在一个安全隐患,如果目标字符串缓冲区不够大
无法容纳所生成的字符串,就会导致内存中数据被破坏,memory corruption
此时应该使用带有安全检测的字符串函数
例子:
C语言所有安全函数的首要任务就是验证传给他们的参数值
检查的项目包括
指针不为null
整数在有效范围内
枚举值是有效的
缓冲区足够容纳结果数据
以上出现错误,就会设置局部于线程的C运行时变量errno
安全函数带有s后缀
Windows当然也提供了一些安全处理字符串的函数,主要是在ShlwApi.h
例子:
相等性比较,推荐使用CompareString(EX)
原型如下
int CompareString(
LCID locale,
DWORD dwCmdFlags,
PCTSTR pString1,
int cch1,
PCTSTRpString2,
int cch2
)
其中 locale ID是用来标识一种语言
使用Unicode的理由:
Unicode有利于应用程序本地化
有利于轻松操纵自己的资源,字符串总是Unicode形式
推荐的字符,字符串处理方式:
开始将文本字符串想象为字符的数组,而不是char或者字节的数组
使用通用数据类型 TCHAR / PTSTR 来表示文本字符和字符串
使用明确的数据类型byte / pbyte来便是字节,字节指针和数据缓冲区
使用TEXT 或者_T 函数来表示字面量字符和字符串,但是为了保持一致或者可读性,避免二者混用
执行全局替换,抛弃PSTR,使用PTSTR
修改与字符串有关的计算:函数经常希望我们传入字符数大小,而不是字节数
应该传入_countof(szBuffer)而不是sizeof(szBuffer)
而内存是按照字节 来分配,所以会根据字符数。malloc(nCharacters*sizeof(TCHAR)
必须始终使用安全的字符串处理函数
不使用不安全的C运行库字符串处理函数
不使用Kernel32方法来进行字符串处理 lstrcat,lstrcpy
我们不能使用基于不安全的缓冲区处理函数来写代码