笔记摘录:Unicode、内核对象
第一章 对程序错误的处理
1、若要确定是什么错误,请调用GetLastError函数:该函数从线程局部存储区获取32位错误代码。
2、Visual studio还配有一个小的实用程序称为Error Lookup,可以使用Error Lookup将错误代码的号码转换成相应文本描述。
3、Windows提供了一个函数FormatMessage,可以将错误代码转换成它的文本描述。Error Lookup调用FormatMessage函数。
4、watch(监视)窗口中选择一行输入$err,hr,它始终显示上一个错误代码和其文本描述。
5、setLastError设置线程的上一个错误代码。
第二章 Unicode
1、软件的本地化要解决的真正问题,实际上就是如何来处理不同的字符集。
2、有些文字和书写规则的字符集中的符号太多了,因此单字节(提供的符号最多不能超过256个)是根本不够的,为此出现了双字节字符集(字符串中的每个字符可以包含一个字节或两个字节)。strlen函数可获得字符串中究竟有多少字节而不能获得有多少字符。
3、Unicode:宽字节字符集,字符串中的所有字符都是16位的(两个字节),因此总共可以得到 65000个字符,远远超过了单字节字符集的256个字符的数目。可以很容易地在不同语言之间进行数据交换 ,提高应用程序的运行效率 。
4、UTF-8编码:可变长编码,把一个unicode字符根据不同数字大小编码成1-6个字节。
5、char:8位ANSI字符(CHAR) wchar_t:16位的unicode字符(WCHAR)。 Unicode数据类型:WCHAR (Unicode字符)、PWSTR(指向Unicode字符串的指针)、PCWSTR(指向一个恒定的Unicode字符串的指针)。
6、TCHAR数据类型可以定义一个ANSI/Unicode通用的字符串数组,如果定义了_UNICODE:typedef wchar_t TCHAR; 未定义_UNICODE:typedef char TCHAR。 ANSI/Unicode通用数据类型:PTSTR和PCTSTR,取决于是否定义宏UNICODE。
7、字符串前面的大写字母L或_T(),用于告诉编译器该字符串应该作为Unicode字符串来编译。
8、Unicode函数均以wcs开头,用前缀wcs来取代ANSI字符串函数的前缀str,如wcscpy = strcpy。 ANSI/Unicode通用字符串函数:lstrcat、lstrcmp、lstrcmpi、lstrcpy、lstrlen,取决于是否定义UNICODE宏。
9、函数名末尾的W表明该函数接受Unicode字符串,A表明接受ANSI字符串,如CreateWindowExA、CreateWindowExW
10、成为符合ANSI和Unicode的应用程序基本原则 :
* 将文本串视为字符数组,而不是chars数组或字节数组;
* 将通用数据类型(如TCHAR和PTSTR)用于文本字符和字符串;
* 将显式数据类型(如BYTE和PBYTE)用于字节、字节指针和数据缓存;
* 将TEXT宏用于原义字符和字符串;
* 执行全局性替换(例如用PTSTR替换PSTR);
* 传递一个缓存的大小: sizeof(szBuffer)/sizeof(TCHAR), 为字符串按字节来分配内存块: malloc(nCharacters *sizeof(TCHAR));
*使用后缀为_s的安全字符串处理函数
第三章 内核对象
1、每个内核对象只是内核分配的一个内存块,并且只能由该内核访问。该内存块是一种数据结构,它的成员负责维护该对象的各种信息。
2、应用程序如何才能操作这些内核对象呢?
当调用一个用于创建内核对象的函数时,该函数就返回一个用于标识该对象的句柄。进程中的任何线程都可以使用这个句柄值,将句柄传递给Windows的各个函数,系统就能知道你想操作哪个内核对象。
3、每个内核对象都包含一个数据成员:使用计数。当一个对象刚刚创建时,它的使用计数被置为1。当另一个进程访问一个现有的内核对象时,使用计数就递增1。如果内核对象的使用计数降为0,内核就撤消该对象(进程终止运行,内核对象不一定被撤消)。
4、安全描述符用于描述谁创建了该对象,谁能够访问或使用该对象,谁无权访问该对象。用于创建内核对象的函数几乎都有一个指向SECURITY_ATTRIBUTES结构的指针作为其参数,大多数应用程序只是为该参数传递NULL,创建带有默认安全性的内核对象。
5、应用程序也可使用其他类型的对象,如菜单、窗口、鼠标光标、刷子和字体等。这些对象属于用户对象或图形设备接口GDI对象。如何确定一个对象是否属于内核对象?
观察创建该对象所用的函数,创建内核对象的所有函数都有一个设定安全属性的参数。用于创建用户对象或GDI对象的函数都没有PSECURITY_ ATTRIBUTES参数。
6、当进程初次被初始化时,系统为它分配一个空的句柄表。当进程中的线程调用创建内核对象的函数时,内核就为该对象分配一个内存块,并对它初始化,同时内核扫描进程的句柄表,找出一个空项放入。创建内核对象的函数返回与进程相关的句柄,该句柄值实际上是放入进程的句柄表中的索引。如果调用函数创建内核对象失败,返回的句柄值是NULL(0),调用CreateFile时未能打开指定的文件,返回值是INVALID_HANDLE_VALUE(-1)。
7、通过调用CloseHandle来关闭内核对象,该函数首先检查调用进程的句柄表,如果传递给它的索引(句柄)是有效的,那么系统就可以获得内核对象的数据结构的地址,并可确定该结构中的使用计数。如果使用计数是0,该内核便从内存中撤消该内核对象。
8、CloseHandle(hFile),关闭句柄后,只是创建的句柄表记录项被清除,不能再次访问,但是该变量保存的值还存在,所以应将hFile = NULL。
9、在不同进程中运行的线程需要共享内核对象,需要共享的原因:
1)文件映射对象使你能够在同一台机器上运行的两个进程之间共享数据块。
2)邮箱和指定的管道使得应用程序能够在连网的不同机器上运行的进程之间发送数据块。
3)互斥对象、信标和事件使得不同进程中的线程能够同步它们的连续运行,这与一个应用程序在完成某项任务时需要将情况通知另一个应用程序的情况相同。
10、共享跨越进程边界的内核对象的三种方法:
对象句柄的继承性:若要创建能继承的句柄,父进程必须指定一个SECURITY_ATTRIBUTES结构,并对它初始化时将bInheritHandle成员置为TRUE,然后将该结构的地址传递给特定的Create函数。
给对象命名:A进程调用Create*函数参数pszName为NULL时,就向系统指明创建一个匿名内核对象,若要按名字共享对象,则为pszName参数传递一个以0结尾的字符串名字的地址。B进程可通过调用Create*函数或Open*函数共享对象。
使用DuplicateHandle函数:该函数取出一个进程的句柄表中的项目,并将该项目拷贝到另一个进程的句柄表中。