第一章 错误处理
所有范例代码可以从 http://www.wintellect.com/books.aspx下载 (改网址已经不可用)
网盘:https://pan.baidu.com/s/1nv1HxxB
本章内容
定义自己的错误代码
ErrorShow实例程序
常见的Windows函数返回值的数据类型
VOID 这个函数不可能失败。只有极少数windows函数的返回类型为VOID
BOOL 如果函数失败,返回0.否则返回一个非0值。避免测试返回值为TRUE应该测试返回是否FALSE
HANDLE 如果函数失败,通常返回NULL,否则HANDLE将返回一个可操作的对象。但是某些函数会返回 INVALID_HANDLE_VALUE(-1)
PVOID 如果调用失败,返回NULL;否之指向一个内存地址
LONG/DWORD (可能返回0或者-1) 具体要参阅MSDN
通常情况下Windows函数能返回错误代码,将有助于我们理解函数调用为什么会失败。
Windows采用Thread Local Storage机制保存一个跟线程相关的错误代码,可以调用GetLastError获取
通常的错误信息定义与WinError.h中
摘录部分
//
// 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: ERROR_PATH_NOT_FOUND
//
// MessageText:
//
// The system cannot find the path specified.
//
#define ERROR_PATH_NOT_FOUND 3L
//
// MessageId: ERROR_TOO_MANY_OPEN_FILES
//
// MessageText:
//
// The system cannot open the file.
//
#define ERROR_TOO_MANY_OPEN_FILES 4L
//
// MessageId: ERROR_ACCESS_DENIED
//
// MessageText:
//
// Access is denied.
//
#define ERROR_ACCESS_DENIED 5L
每个错误有3种表示:一个消息ID(一个宏),消息文本(消息的描述) 和一个编号(避免使用此编号使用错误的ID宏)
在Windows函数调用失败以后,应该马上调用GetLastError。否则调用另一个Windows函数可能会改写此值。
例如CreateEvent,如果已经存在此具名事件,GetLastError返回ERROR_ALREADY_EXISTS
在VISUAL Studio中可以配置Watch 输入 $err,hr .可以实施监控windows api的执行返回情况
VS还搭载了一个小的使用程序,名为Error lookup。可以讲错误代码转换为文本 VS->Tools->Error Lookup启动该应用(VS 2013中)
如果要像用户显示错误的意思。有一个FormatMessage函数
FormatMessageW(
_In_ DWORD dwFlags,
_In_opt_ LPCVOID lpSource,
_In_ DWORD dwMessageId,
_In_ DWORD dwLanguageId,
_Out_ LPWSTR lpBuffer,
_In_ DWORD nSize,
_In_opt_ va_list *Arguments
);
1.1 定义自己的错误代码
windows的这种错误代码机制也可以用于自己的函数中。
在线程上设置错误代码 VOID SetLastError(DWORD dwErrorCode);
错误代码是一个32位数
错误代码的不同字段
后续章节再详细讨论错误代码的含义。 注意29位。 windows api此位设置为0, 客户定义的代码设置为1.
Facility代码可以容纳4096个错误,前256个值为Microsoft保留的,其余可由我们自己ID应用程序来定义。
1.2 ErrorShow示例程序
演示了一个如何显示错误消息的例子
ErrorShow.cpp
/*
Module: ErrorShow.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
*/
#include "..\CommonFiles\CmnHdr.h"
#include
#include
#include "resource.h"
///////////////////////////////////////////////////////////////////////////////
#define ESM_POKECODEANDLOOKUP (WM_USER + 100)
const TCHAR g_szAppName[] = TEXT("Error Show");
///////////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam){
chSETDLGICONS(hwnd, IDI_ERRORSHOW);
// don't accept error codes more than 5 digits long
Edit_LimitText(GetDlgItem(hwnd, IDC_ERRORCODE), 5);
// look up the command-line passed error number
SendMessage(hwnd, ESM_POKECODEANDLOOKUP, lParam, 0);
return (TRUE);
}
///////////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify){
switch (id){
case IDCANCEL:
EndDialog(hwnd, id);
break;
case IDC_ALWAYSONTOP:
SetWindowPos(hwnd, IsDlgButtonChecked(hwnd, IDC_ALWAYSONTOP)
? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
break;
case IDC_ERRORCODE:
EnableWindow(GetDlgItem(hwnd, IDOK), Edit_GetTextLength(hwndCtl) > 0);
break;
case IDOK:
// 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);
//DWORD systemLocale = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
// 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);
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."));
}
break;
}
}
///////////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
case ESM_POKECODEANDLOOKUP:
SetDlgItemInt(hwnd, IDC_ERRORCODE, (UINT)wParam, FALSE);
FORWARD_WM_COMMAND(hwnd, IDOK, GetDlgItem(hwnd, IDOK), BN_CLICKED,
PostMessage);
SetForegroundWindow(hwnd);
break;
}
return (FALSE);
}
///////////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) {
HWND hwnd = FindWindow(TEXT("#32770"), TEXT("Error Show"));
if (IsWindow(hwnd)) {
// An instance is already running, activate it and send it the new #
SendMessage(hwnd, ESM_POKECODEANDLOOKUP, _ttoi(pszCmdLine), 0);
}
else {
DialogBoxParam(hinstExe, MAKEINTRESOURCE(IDD_ERRORSHOW),
NULL, Dlg_Proc, _ttoi(pszCmdLine));
}
}
FormatMessage函数
传入FORMAT_MESSAGE_FROM_SYSTEM 获取系统定义的错误代码对应的字符串
FORMAT_MESSAGE_ALLOCATE_BUFFER,要求函数分配一块足矣容纳文本描述的内存。存于hlocal
FORMAT_MESSAGE_IGNORE_INSERTS 允许获得占位符信息。例如
LANG_NEUTRAL 和 SUBLANG_NEUTRAL 组合表示系统默认语言。
这种情况下,我们不能硬编码一种特定的语言,因为不知道操作系统安装的语言是什么。
如果FormatMessage成功会返回获取的字符串,若失败在搜索是否是网络消息netmsg.dll
每个dll都有自己的错误代码,可以像自己的模块添加代码。使用Message Compiler(MC.exe)创建消息资源并添加到dll中。