一、对程序错误的处理
调用Windows函数时,为是查看函数是否调用成功,通常查看函数的返回值来确定是否成功,还可以使用GetLastError()来确定是什么具体问题。Windows函数运行失败时,应立即调用GetLastError(),否则可能被别的函数修改。
在VS中调试时,在watch窗口输入"@err,hr",就能查看到当前的错误代码和错误详细描述。
使用FormatMessage函数也可以从错误代码中获得错误的详细描述。
二、Unicode
Unicode字符串中的所有字符都是16位(2字节)的。像Windows2000是基于Unicode开发的,所以系统函数都用Unicode字符串。如果在调用时传入一个ANSI字符串,那么系统会首先将字符串转成Unicode,然后将Unicode字符串传递给系统。同样,如果需要返回ANSI字符串,系统首先会将Unicode字符串转换成ANSI字符串再返回。(这些转换由系统完成,需要占用系统的时间和内存)。
COM要求必须使用Unicode。
Unicode字符的数据类型:typedef unsigned short wchar_t;
所有的Unicode字符串函数都是以wcs开头(宽字符串的缩写),调用Unicode函数,只需要将ANSI字符串函数的前缀str替换成wcs即可。
若要定义ANSI/Unicode的字符串,使用TCHAR数据类型。TCHAR在定义了_UNICODE时,
typedef wchar_t TCHAR;
否则,
typedef char TCHAR;
Mircrosoft的C++编译器的默认设置能编译所有的字符串,作为ANSI,因为对于常量字符串,会被作为ANSI字符串,要转换成Unicode,需要在前面加L:
TCHAR* szError = L"Error";
为了通用性,在定义了_UNICODE时,
#define _TEXT(x) L##x
在没有定义_UNICODE时,
#define _TEXT(x) x
这样,无论是否定义了_UNICODE,都可以使用:
TCHAR* szError = _TEXT("Error");
UNICODE和ANSI之间的字符串转换函数有:MultiByteToWideChar和WideCharToMultiByte.
三、内核对象
内核对象是内核分配的一块内存,并且只能由该内核访问。该内存块是一种数据结构,它的数据成员负责维护该对象的各种信息。操作内核对象都会有相应的系统函数。
内核对象包含使用计数,这样就知道有多少个进程在使用它。当一个对象刚创建时,使用计数是1,然后当另一个进程访问一个现有的对象时,使用计数就会增加1,当进程终止时,内核自动确定所有该进程打开的内核对象的使用计数,如果使用计数降为0,内核就撤消该内核对象。
用于创建内核对象的函数几乎都有一个指向SECURITY_ATTRIBUTES结构的指针作为其参数。
typedef struct _SECURITY_ATTRIBUTES{
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;
当进程被初始化时,系统会为它分配一个句柄表,该句柄表只用于内核对象,不用于用户对象或GDI对象。
句柄表的每项包含一个指向内核对象的指针、一个访问屏蔽和一些标志。
进程创建一个内核对象时,内核扫描进程的句柄表,找出一个空项,将这个新创建的内核对象的相关信息保存其中。创建内核对象如果失败了,通常返回值是NULL,只有当调用函数CreateFile时,失败返回值为INVALID_HANDLE_VALUE。
CloseHandle用来结束对该对象的操作,在该函数返回之前,它会清除进程的句柄表中的项目,于是句柄对该进程无效,不要再试图使用它。如果内核对象的使用计数降为0,内核会撤销这个内核对象。
CreateProcess函数有一个参数为bInheritHandle,它用来确定是否继承父进程的句柄表,如果是,系统会遍历父进程的句柄表的每一项,将其中有效的项准确的拷贝到子进程中。当然,还会递增内核的使用计数。这样,如果将某个句柄值通过命令行参数或发送消息的方式传递给子进程,子进程就可以获得这个内核对象的使用权。
如果要改变句柄的标志,可以使用函数SetHandleInformation。
复制对象句柄:DuplicateHandle. 顾名思义,这个函数可以将一个进程中的句柄表的项拷贝到另一个进程的句柄表中。