要查看具体是什么错误,请调用 GetLastError 函数:DWORD GetLastError(); 它的作用很简单,就是返回由上一个函数调用设置的线程的 32 位错误代码。
一个Windows函数失败之后,应该马上调用GetLastError,因为假如又调用了另一个Windows函数,则此值很可能被改写。注意,成功调用的 Windows 函数可能用 ERROR_SUCCESS改写此值。
Windows 提供了一个函数,可以将错误代码转换为相应的文本描述。此函数名为 FormatMessage
Windows之所以使用UTF-16,是因为全球各地使用的大部分语言中,每个字符很容易用一个16位值来表示。这样一来,应用程序很容易遍历字符串并计算出它的长度。但是,16位不足以表示某些语言的所有字符。对于这些语言,UTF-16支持使用代理(surrogates),后者是用32位(或者说4个字节)来表示一个字符的一种方式。由于只有少数应用程序需要表示这些语言中的字符,所以UTF-16在节省空间和简化编码这两个目标之间,提供了一个很好的折衷。注意,.NET Framework始终使用UTF-16来编码所有字符和字符串,所以在你自己的Windows应用程序中,如果需要在原生代码(native code)和托管代码(managed code)之间传递字符或字符串,使用UTF-16能改进性能和减少内存消耗。
另外还有其他用于表示字符的UTF标准,具体如下。
UTF-8 UTF-8将一些字符编码为1个字节,一些字符编码为2个字节,一些字符编码为3个字节,一些字符编码为4个字节。值在0x0080以下的字符压缩为1个字节,这对美国使用的字符非常适合。0x0080和0x07FF之间的字符转换为2个字节,这对欧洲和中东地区的语言非常适用。0x0800以上的字符都转换为3个字节,适合东亚地区的语言。最后,代理对(surrogate pairs)被写为4个字节。UTF-8是一种相当流行的编码格式。但在对值为0x0800及以上的大量字符进行编码的时候,不如UTF-16高效。
UTF-32 UTF-32将每个字符都编码为4个字节。如果打算写一个简单的算法来遍历字符(任何语言中使用的字符),但又不想处理字节数不定的字符,这种编码方式就非常有用。例如,如果采用UTF-32编码方式,就不需要关心代理(surrogate)的问题,因为每个字符都是4个字符。显然,从内存使用这个角度来看,UTF-32 并不是一种高效的编码格式。因此,很少用它将字符串保存到文件或传送到网络。这种编码格式一般在应用程序内部使用。
修改字符串算术问题。例如,函数经常希望你传给它缓冲区的字符数,而不是字节数。这意味着你应该传入_countof(szBuffer),而不是sizeof(szBuffer)。而且,如果需要为一个字符串分配一个内存块,而且知道字符串中的字符数,那么记住内存是以字节来分配的。这意味着你必须调用malloc(nCharacters
sizeof(TCHAR)),而不是调用malloc(nCharacters)。在前面列出的所有基本准则中,这是最难记住的一条,而且如果出错,编译器不会提供任何警告或错误信息。所以,最好定义一个宏来避免犯错:#define chmalloc(nCharacters) (TCHAR)malloc(nCharacters * sizeof(TCHAR)).
始终使用安全的字符串处理函数,比如那些后缀为_s的,或者前缀为StringCch的。后者主要在你想明确控制截断的时候使用;如果不想明确控制截断,则首选前者。
在我们的代码中,需要要比较两种字符串。其中,编程类的字符串包括文件名、路径、XML元素/属性以及注册表项/值等等。对于这些字符串,应使用CompareStringOrdinal来进行比较。因为它非常快,而且不会考虑用户的区域设置。这是完全合理的,因为不管程序在世界上的什么地方运行,这种字符串都是不变的。用户字符串则一般要在用户界面上显示。对于这些字符串,应使用CompareString(Ex)来比较,因为在比较字符串的时候,它会考虑用户的区域设置。
由于内核对象的数据结构只能由内核访问,所以应用程序不能在内存中定位这些数据结构并直接更改其内容。Microsoft 有意强化了这个限制,确保内核对象结构保持一致性状态。正是因为有这个限制,所以 Microsoft 能自由地添加、删除或修改这些结构中的成员,同时不会干扰任何应用程序的正常运行。
既然不能直接更改这些结构,应用程序如何操纵这些内核对象呢?答案是利用 Windows 提供的一组函数,以经过良好定义的方式来操纵这些结构。使用这些函数,始终可以访问这些内核对象。调用一个会创建内核对象的函数后,函数会返回一个句柄(handle),它标识了创建的对象。可以将这个句柄想象为一个不透明(opaque)的值,它可由进程中的任何线程使用。在 32 位 Windows 进程中,句柄是一个 32 位值;在 64 位 Windows 进程中,则是一个 64 位值。可将这个句柄传给各种 Windows 函数,告诉系统你想操纵哪一个内核对象。
为了增强操作系统的可靠性,这些句柄值是与进程相关的
内核对象的所有者是内核,而非进程
要想判断一个对象是不是内核对象,最简单的方式是查看创建这个对象的函数。几乎所有创建内核对象的函数都有一个允许你指定安全属性信息的参数
相反,用于创建 User 或 GDI 对象的函数都没有 PSECURITY_ATTRIBUTES 参数
由于句柄值实际是作为进程句柄表的索引来使用的,所以这些句柄是相对于当前这个进程的,无法供其他进程使用。如果你真的在其他进程中使用它,那么实际引用的是那个进程的句柄表的同一个索引位置处的内核对象——只是索引值相同而已,你根本不知道它会指向什么对象。
无论以什么方式创建内核对象,都要调用 CloseHandle 向系统指出你已经结束使用对象,如下所示:
BOOL CloseHandle(HANDLE hobject);
就在 CloseHandle 函数返回之前,它会清除进程句柄表中的记录项——这个句柄现在对你的进程来说是无效的,不要再试图用它。无论内核对象当前是否销毁,这个清除过程都会发生!一旦调用 CloseHandle,你的进程就不能访问那个内核对象;但是,如果对象的使用计数没有递减至 0,它就不会被销毁。这是完全正常的;它表明另外还有一个或多个进程在使用该对象。当其他进程全部停止使用这个对象后(通过调用 CloseHandle),对象就会被销毁。通常,在创建一个内核对象时,我们会将它的句柄保存到一个变量中。将此变量作为参数调用了 CloseHandle 函数后,还应同时将这个变量设为 NULL
利用三种不同的机制来允许进程共享内核对象:使用对象句柄继承;为对象命名;以及复制对象句柄。
对象句柄的继承只会在生成子进程的时候发生。假如父进程后来又创建了新的内核对象,并同样将它们的句柄设为可继承的句柄。那么正在运行的子进程是不会继承这些新句柄的。