当我们调用Windows函数时,它会先验证我们传给它的参数,然后开始执行任务。
如果传入的参数无效,或者由于其他原因导致操作无法执行,则函数的返回值将指出函数因为某些原因失败了。
下表暂时了大多数Windows函数使用的返回值的数据类型:
在内部,当Windows函数检测到错误时,它会使用一种名为“线程本地存储”的机制将相应的错误代码与“主调线程”关联到一起。这种机制使不同的线程能独立运行,不会出现相互干扰对方的错误代码的情况。
函数返回时,其返回值会指出已发生一个错误。
要具体查看是什么错误,需要调用 GetLastError 函数:
1 |
DWORD GetLastError( VOID ); |
函数作用是返回上一个函数调用设置的线程32位错误代码。
这里截取了 <WinError.h>头文件里的部分错误代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// // Define the severity codes // // // MessageId: ERROR_SUCCESS // // MessageText: // // The operation completed successfully. // #define ERROR_SUCCESS 0L #define NO_ERROR 0L // dderror #define SEC_E_OK ((HRESULT)0x00000000L) // // MessageId: ERROR_INVALID_FUNCTION // // MessageText: // // Incorrect function. // #define ERROR_INVALID_FUNCTION 1L // dderror // // MessageId: ERROR_FILE_NOT_FOUND // // MessageText: // // The system cannot find the file specified. // #define ERROR_FILE_NOT_FOUND 2L |
可以看到,每个错误都有三种表示:
- MessageId: 一个消息ID,它是一个可以在源代码中使用的宏
- MessageText: 消息文本,它是描述错误的英文文本
- 编号: 应尽量避免使用它,我们可以看到,ERROR_SUCCESS 的编号是 0L
Windows核心编程说的39 000行还是少了,目前已经增加到了约 54 600 行…
当Windows函数调用失败后,应马上调用 GetLastError,因为加入又调用了另一个Windows函数,则此值可能改写。
注意:成功调用的Windows函数可能用 ERROR_SUCCESS 改写此值
一些Windows函数的调用成功,可能是缘于不同的原因。例如,创建一个命名的事件内核对象时,以下两种情况均会成功:
- 对象实际创建完成
- 存在一个同名的事件内核对象
Microsoft选择采用 “上一个错误代码”机制来返回这种信息,所以特定函数调用成功时,可以调用 GetLastError 函数来确定额外的信息。
例如对于创建事件内核对象,如果对象已经存在,则会返回 ERROR_ALREADY_EXISTS。
同时,我们也可以在VS中监视这个值,在Watch(监视)窗口中,输入 $err,hr 即可查看。
Windows提供了一个函数,可以将错误代码转换为相应的文本描述。此函数为:
1 2 3 4 5 6 7 8 9 |
DWORD FormatMessage( DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPTSTR lpBuffer, DWORD nSize, va_list *Arguments ); |
该函数的使用会在后讲述。
1.1 定义自己的错误代码
在编程中,我们可能需要写自己的函数供他人调用,这个函数可能会因为某些原因失败,所以需要向调用者指出错误。为了指出错误,只需设置线程上的一个错误代码,然后令自己的函数返回FALSE、INVALID_HANDLE_VALUE、NULL或其他合适值。
通过下面的函数可以传递我们认为合适的任何32位的值:
1 |
VOID SetLastError(DWORD dwErrCode); |
建议尽量使用 <WinError.h> 中现有的代码——只要代码能很好地反映我想报告的错误。
如果其中任何一个代码都不能准确地反映一个错误,就可以创建自己的代码。错误代码是一个32位数。
错误代码的不同字段如下:
要注意的是第29位:
- 如果创建自己的错误代码,就必须在此位放入一个1.
- 如果是Microsoft生成的错误代码,则此位…承诺会一直为0.
注意第 27~16位:前256个值是为Microsoft保留的,其余的值可由我们应用程序来定义。
1.2 ErrorShow 示例程序——FormatMessage 函数的使用
展示处理部分的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
// Get the error code //首先,从界面获取错误代码 DWORD dwError = GetDlgItemInt( hwnd , IDC_ERRORCODE , NULL , FALSE ); //定义一个句柄获取错误信息 HLOCAL hlocal = NULL; // Buffer that gets the error message string // Use the default system locale since we look for Windows messages. // Note: this MAKELANGID combination has 0 as value DWORD systemLocale = MAKELANGID( LANG_NEUTRAL , SUBLANG_NEUTRAL ); // Get the error code's textual description //调用函数获得错误信息 BOOL fOk = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER , NULL , dwError , systemLocale , (PTSTR)&hlocal , 0 , NULL ); //如果没找到,在 NetMsg.dll 中再查找。 if(!fOk) { // Is it a network-related error? HMODULE hDll = LoadLibraryEx( TEXT( "netmsg.dll" ) , NULL , DONT_RESOLVE_DLL_REFERENCES ); if(hDll != NULL) { fOk = FormatMessage( FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER , hDll , dwError , systemLocale , (PTSTR)&hlocal , 0 , NULL ); FreeLibrary( hDll ); } } //返回获取到的信息 if(fOk && ( hlocal != NULL )) { SetDlgItemText( hwnd , IDC_ERRORTEXT , (PCTSTR)LocalLock( hlocal ) ); LocalFree( hlocal ); } else { SetDlgItemText( hwnd , IDC_ERRORTEXT , TEXT( "No text found for this error number." ) ); } |
接下来详细讲述下 上文中 FormatMessage 函数的使用:
1 2 3 4 5 6 7 8 9 10 |
fOk = FormatMessage( FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER , NULL , dwError , systemLocale , (PTSTR)&hlocal , 0 , NULL ); |
第一个参数有三个值,它们的含义分别是:
- FORMAT_MESSAGE_FROM_SYSTEM 希望获得一个系统定义的错误代码对应的字符串。
- FORMAT_MESSAGE_IGNORE_INSERTS 允许获得含有%d占位符的消息
- FORMAT_MESSAGE_ALLOCATE_BUFFER 要求该函数分配一块容纳错误文本描述的内存
要注意的是:
- 存放错误文本内存所对应的句柄在 hlocal 中返回
- %d占位符被Windows用来提供更多上下文相关信息,如果不传递这个表示,就必须在 Arguments 参数中提供这些占位符的值。但是这对于ErrorShow程序来说是不可能的,因为消息的内容事先是未知地。
第三个参数指出想要查找的错误代码。
第四个参数指出要用什么语言来显示文本。以下是参数的获得方法:
1 |
DWORD systemLocale = MAKELANGID( LANG_NEUTRAL , SUBLANG_NEUTRAL ); |
通过 LANG_NEUTRAL 和 SUBLANG_NEUTRAL 生成一个0值——操作系统默认语言。
第五个参数就是我们接受内存的句柄参数。
如果FormatMessage函数调用成功,文本描述就在上文的内存中,由 hlocal 句柄接受它。
如果调用失败,就在NetMsg.dll模块中查找消息代码,看错误是否和网络有关。
利用 NetMsg.dll 模块的句柄,再一次调用FormatMessage函数。
由此可见,每一个 DLL 或 .exe 都可以有自己的错误代码。
其实我们也可以创建自己的错误代码…MessageCompiler 来创建消息资源并添加到DLL或.ext模块中。