::FormatMessage 的 MFC 接口






3.57/5 (13投票s)
这篇文章展示了一个接口,允许从 MFC 轻松访问 ::FormatMessage API。
引言
这段代码实际上几乎出现在我所有的项目中。 然而,一些读者错过了这个,所以我把它放在这里(没有下载,你可以从这个网页复制粘贴)。
这展示了一种从 MFC 接口到 ::FormatMessage
并以 MFC 兼容方式使用它的方法。
问题
处理错误时,一个常见的问题是向最终用户提供完整、有信息量的信息。 很多时候,问题是由这些真正有信息量的消息框报告的
它几乎和说
这些都是真正恶劣的例子,在太多的程序中太常见了。 这样做的问题是,用户不知道如何响应此错误。 因此,用户必须致电技术支持。 这个可怜的受害者告诉技术支持什么? 对话框的内容? 你最好希望你的技术支持人员不知道你的隔间在哪里,否则你可能会在某一天遇到不幸的“事故”。
对于这种可怜的错误消息例子,绝对没有任何借口。 绝对没有。
解决方案
现在,这是一条有用的消息
它说有一个文件打开错误(如果我有多于一种类型的文件,即使那一行也会更精确),它说哪个文件有问题,并且它说明了原因。 用户查看此内容,意识到该文件正在其他程序中打开,然后关闭该程序。 或检查文件的保护是否设置正确。 但不觉得有必要致电技术支持。
你如何获得那个好的结果? 答案是少量的代码行,这就是为什么永远没有借口不产生一个像样的错误消息。
例如,要生成此消息,我做了
BOOL CMyClass::ReadFile(const CString & filename) { CFile f; if(!f.Open(filename, CFile::modeRead)) { /* Failed */ DWORD err = ::GetLastError(); // [1] CString fmt; // [2] fmt.LoadString(IDS_FILE_OPEN_ERROR); // [3] // File open error\n // %s\n // %s CString msg; // [4] msg.Format(fmt, filename, ErrorString(err)); // [5] AfxMessageBox(msg, MB_ICONERROR | MB_OK); return FALSE; } /* Failed */ ... read file here f.Close(); return TRUE; } // CMyClass::ReadFile
这只需要五行代码就可以把事情做好。 因此,对于作为第一个例子给出的完全无能的消息,永远没有理由。
请注意这里的一点。 API 失败后执行的第一行是 ::GetLastError
。 不要在此之前放置 ASSERT
;不要做任何其他事情; 立即捕获该错误。
为了完成此操作,必须编写 ErrorString
函数。 但是,考虑到编写此函数是多么微不足道,而且只需要为您的整个生命周期编写一次,因此没有理由不在任何地方使用它。 为了允许本地化,字符串“Unknown error code %08x (%d)”存储在 STRINGTABLE
中,作为字符串 IDS_UNKNOWN_ERROR
。
我从未理解过这种现象,但第一个具有集成错误处理系统的操作系统,IBM 的 TSS/360,在 1968 年,会在每个错误消息字符串的末尾添加一个 CRLF。 这毫无意义。 如果我想要 CRLF,我可以添加它。 尽管如此,微软在近 20 年后继续这种疯狂。 每条消息都以 CRLF 结尾。 为什么? 没有任何合理的解释。 所以,我必须删除终端 CRLF。
此外,如果你打算在一些无法理解换行序列的上下文中使用消息,例如 ListBox,你可能还希望执行 CString::Replace(_T("\r\n"), _T(" "))
以摆脱多余的内部 CRLF 序列。
ErrorString.h
CString ErrorString(DWORD err);
ErrorString.cpp
#include "stdafx.h" #include "resource.h" #include "ErrorString.h" /**************************************************************************** * ErrorString * Inputs: * DWORD err: Error code * Result: CString * String message ****************************************************************************/ CString ErrorString(DWORD err) { CString Error; LPTSTR s; if(::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, (LPTSTR)&s, 0, NULL) == 0) { /* failed */ // Unknown error code %08x (%d) CString fmt; CString t; fmt.LoadString(IDS_UNKNOWN_ERROR); t.Format(fmt, err, LOWORD(err)); Error = t; } /* failed */ else { /* success */ LPTSTR p = _tcsrchr(s, _T('\r')); if(p != NULL) { /* lose CRLF */ *p = _T('\0'); } /* lose CRLF */ Error = s; ::LocalFree(s); } /* success */ return Error; } // ErrorString
关于 MessageBox/AfxMessageBox 调用的哲学
在我编写的任何程序中,没有任何地方有两处发出相同的 MessageBox/AfxMessageBox
文本。 仅通过查看消息的文本,我就可以立即知道程序的哪一行发出了它。 这很重要。 这使得确定程序的确切行成为可能,该行发出了消息。
有时,如果我有一个通用的错误处理程序,我将传入一个额外的 CString
参数,该参数出现在 MessageBox/AfxMessageBox
中,以便可以识别处理程序例程的调用者。 没有其他方法可以确保每个用户可见的错误情况都与发出它的程序中的站点有 1:1 的对应关系。