1.1 定义自己的错误代码
(1)常见的Windows函数返回值数据类型
数据类型 |
指出函数调用失败的值 |
VOID |
这个函数不可能失败。只有极少数Windows函数的返回值类型为VOID |
BOOL |
如果函数失败,返回值为0;否则,成功时返回值非0。 ★应避免测试返回值是否为TRUE,最稳妥的做法是检查它是否不为FALSE。(因为TRUE在不同的机器上,可能值是不同的,但FALSE却都是0)。 如,if(FALSE!=func)、if(FALSE==func())、if(!func())或if(func())。 但if(TRUE==func)或if(TRUE!=func())不可取。 |
HANDLE |
失败——NULL或INVALID_HANDLE_VALUE(-1):要按MSDN函数说明来判断到底属哪种? 否则——标识一个可操纵的对象。 |
PVOID |
失败——NULL。 成功——标识一个数据块的内存地址 |
LONG/DWORD |
这种类型比例复杂。(要看MSDN中具体函数的说明) eg.返回计数的函数,如果因某种原因不能计数,通常返回0或-1。 |
(2)GetLastError——利用“线程本地存储机制”,使用每个线程的错误代码不会互相干挠,注意,这个错误代码是属于线程的。当API函数调用失败时,这个函数会设置调用该函数的线程(简称为Calling Thread)的错误代码值。即“调用线程”最近的错误代码,部分函数在成功调用时,可能用ERROR_SUCCESS改写此值。(所以,0表示操作成功,没有错误)
①为什么成功调用仍需改写这个“Last Error Code”?
有时创建对象成功,可能是因为内核中存在一个同名的事件内核对象,函数直接返回该对象。但某些情况下,我们需要知道这种情况的发生,所以特定函数调用成功时,Windows通过改写这个Last Error Code的值,用户就通过GetLastError来知道这件事或得到一些额外的信息。
②让VS始终显示线程的上一个错误代码的文本描述。
在“Watch(监视)”窗口中添加一行,在名称栏中输入“$err,hr”,其中的“,hr”表示列出错误代码的文本描述。
③错误消息:包含消息ID、消息编号、消息文本;
eg.#define ERROR_SUCCESS 0L;//消息ID为ERROR_SUCCESS,编号为0L。
(3)FormatMessage——将错误代码转换为相应的文本描述(可以转成指定语言的文本,如中文!)
参数 |
含义 |
DWORD dwFlags |
格式化选项,对lpSource参数值有指导作用 ①FORMAT_MESSAGE_ALLOCATE_BUFFER:为接收的描述字符串分配内存。 ②FORMAT_MESSAGE_FROM_SYSTEM:在系统的id映射表中寻找描述字符串 ③FORMAT_MESSAGE_FROM_HANDLE:在其他资源模块(eg.dll)中寻找描述字符串 ④FORMAT_MESSAGE_FROM_STRING:消息ID是个字符串,不是DWORD ⑤FORMAT_MESSAGE_IGNORE_INSERTS:允许我们获得含有%占位符的消息,不传递这个标志,就必须在Arguments参数中提供这些占位符信息。(即直接返回含占位符的错误文本)。 通常为:FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
LPCVOID lpSource |
这个值是消息资源表示来自哪里,当dwFlags为FORMAT_MESSAGE_FROM_HANDLE时该参数表示模块的句柄。为FORMAT_MESSAGE_FROM_STRING时,通常为NULL |
DWORD dwMessageId |
消息ID。如果指定了FORMAT_MESSAGE_STRING时,该项将被忽略。 |
DWORD dwLanguageId |
消息描述所用的语言。通常0表示自动选择。 |
LPTSTR lpBuffer |
如果未指定FORMAT_MESSAGE_ALLOCATE_BUFFER,则要我们自己提供缓冲区。否则为系统LocalAlloc分配,但用完后,仍需要被调用LocalFree来释放缓冲区。 |
DWORD nSize |
当指定FORMAT_MESSAGE_ALLOCATE_BUFFER时,为系统分配的最小缓冲区大小。 否则为我们指供的缓冲区的大小。(以TCHARs为单位的) |
va_list *Arguments |
在消息定义中的插入序列将会被忽略,这个标示符在获取一个格式化好的消息十分有用。如果这个标示符设置好了,那么Arguments参数将被忽略。一般可设为NULL。 |
(4)定义自己的错误代码——可用于我们自己的函数,而不是API函数。
①VOID SetLastError(DWORD dwErrCode)——错误代码的格式
位 |
31-30 |
29 |
28 |
27-16 |
15-0 |
内容 |
严重性 |
Microsoft/客户 |
保留 |
Facility代码 |
异常代码 |
含义 |
0=成功 1=信息(提示) 2=警告 3=错误 |
0=Microsoft定义的代码。 1=客户定义的代码 |
必须为0 |
前256个值由Microsoft保留。 可查看winerror.h文件 |
Microsoft/客户定义的代码 |
1.2 ErrorShow示例程序
(1)消息分流器(Messasge Craker)——使窗口过程代码更简洁,逻辑更清晰。
①HANDLE_MSG宏及问题(该宏一般用在WndProc而不能用在DlgProc中,见后面分析)
#define HANDLE_MSG(hwnd, message, fn)
case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
A、如果调用HANDLE_MSG(hwnd,WM_INITDIALOG,Dlg_InitDialog),而其中的
HANDLE_##message相当于HANDLE_WM_INITDIALOG宏,其定义如下:
#define HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, fn)
(LRESULT)(DWORD)(UINT)(BOOL)(fn)((hwnd), (HWND)(wParam), lParam)
而从上面宏定义可以看出返回值为BOOL型,该调用如果用在DlgProc中没问题,但是
B、当处理HANDLE_WM_COMMAND宏时,返回值为0L(注意逗号表达式),这对于一般的窗口过程WndProc来说WM_COMMAND中返回0没问题,但对于对话框过程来说,就出问题了,因为WM_COMMAND应return TRUE或break,而不一定是FALSE(TRUE表示该消息处理过了),这就是 HANDLE_MSG的问题!
#define HANDLE_WM_COMMAND(hwnd, wParam, lParam, fn) /
((fn)((hwnd), (int)(LOWORD(wParam)), (HWND)(lParam),
(UINT)HIWORD(wParam)), 0L)
②chHANDLE_DLGMSG宏:对于对话框消息处理过程要使用SetDlgMsgResult宏来正确返回。这就是大牛Jeffery定义的chHANDLE_DLGMSG宏,其中封装了SetDlgMsgResult。这个chHANDLE_DLGMSG宏会正确返回值或句柄。该宏一般在对话框过程中使用。
#define chHANDLE_DLGMSG(hWnd, message, fn)
case (message): return (SetDlgMsgResult(hWnd, uMsg,
HANDLE_##message((hWnd), (wParam), (lParam), (fn))))
//DWLP_MSGRESULT,替换内置对话框过程的返回值
#define SetDlgMsgResult(hwnd, msg, result) ((
(msg) == WM_CTLCOLORMSGBOX ||
(msg) == WM_CTLCOLOREDIT ||
(msg) == WM_CTLCOLORLISTBOX ||
(msg) == WM_CTLCOLORBTN ||
(msg) == WM_CTLCOLORDLG ||
(msg) == WM_CTLCOLORSCROLLBAR ||
(msg) == WM_CTLCOLORSTATIC ||
(msg) == WM_COMPAREITEM ||
(msg) == WM_VKEYTOITEM ||
(msg) == WM_CHARTOITEM ||
(msg) == WM_QUERYDRAGICON ||
(msg) == WM_INITDIALOG
) ? (BOOL)(result) : (SetWindowLongPtr((hwnd), DWLP_MSGRESULT, (LPARAM)(LRESULT)(result)), TRUE)) //(注意逗号表达式里返回的是TRUE)
可见,如果消息是中的一个,那么返回值就是我们写的fn函数的返回值,如果消息不是其中的,那么先执行SetWindowLongPtr把对话框中消息处理程序的返回值设置为fn函数的返回值,最后返回TRUE告诉对话框内置消息过程该消息己经处理过了。(根据逗号操作符特性)。
为什么要这么做呢?这些WM_CTLCOLORMSGBOX 、 WM_CTLCOLOREDIT……有什么特殊的吗? 这些消息的返回值都是有已知的且有自己含义的,对于这些消息只需要返回它们函数处理的返回值就让程序正常运行。但对于消息WM_COMMAND就很难说了,很有可能是某个控件的通知消息,需要一个值,这个值可能是零也可能不是零,所以不能像WM_CTLCOLORMSGBOX那样处理了,因为对话框过程中TRUE大部分时候代表用户处理此消息,FALSE或0代表用户没处理此消息,那么系统内置的对话框处理过程会进行默认处理。这样的话,如果一个WM_COMMAND的处理消息需要返回0,怎么办?这样办,先返回TRUE,然后用SetWindowLongPtr设置需要返回的返回值,返回TRUE是向内置对话框过程表明该消息已经处理了,但返回值最后被代替成了SetWindowlongPtr的值,这样才能正常工作。
【延伸】Windows内的对话框消息过程。
LRESULT CALLBACK DefDlgProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//获取自定义的对话框过程dp
DLGPROC dp = (DLGPROC)GetWindowLongPtr(hdlg, DWLP_DLGPROC);//dp为对话框过
SetWindowLongPtr(hdlg, DWLP_MSGRESULT, 0); //设置内置消息过程的返回值
INT_PTR fResult = dp(hdlg, uMsg, wParam, lParam); //调用对话框过程
//从SetDlgMsgResult可知,dp返回的是TRUE或FALSE,但真正的返回值保存在
//DWLP_MSGRESULT中。
if (fResult) return GetWindowLongPtr(hdlg, DWLP_MSGRESULT); //如果处理过了
//从SetDlgMsgResult的处理可知dp处理完后,
// SetDlgMsgResult返回TRUE给fResult,同时
// DWLP_MSGRESULT被设置我们的自定义的返回值
//然后通过Get这个返回值,返回给DefDlgProc。
else ...做默认的事... //FALSE,没处理,则进行默认处理
}
(2)FORWARD_WM_COMMAND宏
#define FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, fn)
(void)(fn)((hwnd), WM_COMMAND, MAKEWPARAM((UINT)(id),
(UINT)(codeNotify)), (LPARAM)(HWND)(hwndCtl))
★模拟“查询”按钮点击(通过发送WM_COMMAND消息)
FORWARD_WM_COMMAND(hwnd, IDOK, GetDlgItem(hwnd, IDOK), BN_CLICKED,
PostMessage);//PostMessage为系统API
【ErrorShow程序】
/*----------------------------------------------------------------------------- Module: ErrorShow.cpp Notices: Copyright(c)2008 Jeffery Richter & Christophe Nasarre ------------------------------------------------------------------------------*/ #include "....CommonFilesCmnHdr.h" //里面包含Windows.h、CommCtrl.h和process.h #include "resource.h" #include <tchar.h> //////////////////////////////////////////////////////////////////////////////// #define ESM_POKECODEANDLOOKUP (WM_USER + 100) const TCHAR g_szAppName[] = TEXT("Error Show"); INT_PTR CALLBACK AboutDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); //////////////////////////////////////////////////////////////////////////////// BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) { chSETDLGICONS(hwnd, IDI_ERRORSHOW); //限制错误代码在5位长度以内 Edit_LimitText(GetDlgItem(hwnd, IDC_ERRORCODE), 5); //查找命令行传入的错误代码号 SendMessage(hwnd, ESM_POKECODEANDLOOKUP, lParam, 0); return TRUE; } //根据不同的语言来显示错误文本描述 BOOL ShowErrorText(HWND hwnd,int idCtrl, DWORD dwLangID,DWORD dwError) { HLOCAL hlocal = NULL; //获得错误文本描述的缓冲区 HMODULE hDll = NULL; BOOL iRet = TRUE; //获取错误文本描述 BOOL fOk = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, dwError, dwLangID, (PTSTR)&hlocal, 0, NULL); if (!fOk) //查询失败,可能是网络相关的错误代码 { hDll = LoadLibraryEx(TEXT("netmsg.dll"), NULL, DONT_RESOLVE_DLL_REFERENCES); if (hDll != NULL) { //注意:改为FORMAT_MESSAGE_FROM_HMOUDLE,并传hdll给lpSource参数 fOk = FormatMessage( FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, hDll, dwError, dwLangID, (PTSTR)&hlocal, 0, NULL); FreeLibrary(hDll); } } iRet = fOk && (hlocal != NULL); if (iRet) { SetDlgItemText(hwnd, idCtrl, (PCTSTR)LocalLock(hlocal)); LocalFree(hlocal); } return iRet; } //////////////////////////////////////////////////////////////////////////////// //id =LOWORD(wParam); //codeNotify =HIWORD(wParam) //hwndCtrl = lParam; void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotity) { switch (id) { case IDC_QUIT: EndDialog(hwnd, id); break; case IDC_ABOUT: DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc); 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(hwndCtrl) > 0); break; case IDOK: //获取错误代码 DWORD dwError = GetDlgItemInt(hwnd, IDC_ERRORCODE, NULL, FALSE); //使用默认语言(中文)来查看错误文本描述,MAKELANGID组合一个0值 DWORD dwLangID = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); if (!ShowErrorText(hwnd, IDC_ERRORTEXT_CN, dwLangID, dwError)) SetDlgItemText(hwnd, IDC_ERRORTEXT_CN, TEXT("没有发现这个错误代码的描述文本")); //使用美国英语来查看错误文本描述 dwLangID = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); if (!ShowErrorText(hwnd, IDC_ERRORTEXT_US, dwLangID, dwError)) SetDlgItemText(hwnd, IDC_ERRORTEXT_US, TEXT("No text found for this error number.")); break; } } ////////////////////////////////////////////////////////////////////////////////// //注意,一般对话框过程是返回BOOL型的,但这里经过chHANDLG_DLGMSG宏处理后 //会返回:值或句柄 INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { //以下两句代码在VS2013下,“IntelliSense”会提示错误,但实际上这并没错 //因为这里的WM_INIDIALOG会被“智能感知”错误地认为会被宏替宏为0x0110 //但实际上,因为chHANDLE_DLGMSG内部是通过HANDL_##message连接的,所以 //编译器仍然会正确解析。因此如果觉得不爽,可以禁用IntelliSense方法是 //工具→选项→文本编辑器→C/C++→高级→禁用IntelliSense或禁用波形曲线 chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); case ESM_POKECODEANDLOOKUP: //命令行传递过来的查询代码 SetDlgItemInt(hwnd, IDC_ERRORCODE, (UINT)wParam, FALSE); //模拟“查询”按钮点击(通过发送WM_COMMAND消息) FORWARD_WM_COMMAND(hwnd, IDOK, GetDlgItem(hwnd, IDOK), BN_CLICKED, PostMessage); //该函数将创建指定窗口的线程设置到前台,并且激活该窗口 SetForegroundWindow(hwnd); break; case WM_CLOSE: EndDialog(hwnd, IDC_QUIT); break; } return FALSE; } //“关于”对话框 INT_PTR CALLBACK AboutDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_COMMAND: switch (LOWORD(wParam)) { case IDCANCEL: EndDialog(hwnd, 0); return TRUE; } case WM_CLOSE: EndDialog(hwnd, 0); return TRUE; } return FALSE; } //////////////////////////////////////////////////////////////////////////////// int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int) { //第1个参数:#32270表示对话框类名,Windows内部注册的窗口类名 //第2个参数:窗口名称 HWND hwnd = FindWindow(TEXT("#32270"), TEXT("Error Show")); if (IsWindow(hwnd)) //判断定给定的窗口句柄是否标识一个已存在的窗口 { //己经存在一个正在运行的实例,激活该窗口并设置编辑框中的错误为wParam,然后查询 //int i=_ttoi("123");将字符串转为整型 SendMessage(hwnd, ESM_POKECODEANDLOOKUP, _ttoi(pszCmdLine), 0); } else DialogBoxParam(hInstExe, MAKEINTRESOURCE(IDD_ERRORSHOW), NULL, Dlg_Proc,_ttoi(pszCmdLine)); return 0; }
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ 生成的包含文件。 // 供 ErrorShow.rc 使用 // #define IDD_ERRORSHOW 101 #define IDI_ERRORSHOW 102 #define IDD_ABOUT 103 #define IDC_ERRORCODE 1001 #define IDC_ALWAYSONTOP 1002 #define IDC_ERRORTEXT_CN 1003 #define IDC_ERRORTEXT_US 1004 #define IDC_ABOUT 1005 #define IDC_QUIT 1006 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 104 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1006 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
//ErrorShow.rc
// Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // 中文(简体,中国) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h " END 2 TEXTINCLUDE BEGIN "#include ""winres.h"" " "