AMX – 应用程序消息和异常处理






4.50/5 (4投票s)
一个 C++ 类,用于检索和显示已编译的消息文件。
引言
与许多学科(不仅仅是软件设计)一样,最后才得到足够重视的是错误处理、消息传递和用户反馈。通常,代码中散布着类似 MessageBox(“您忘记登录了”) 这样的语句。随着项目进展,人们突然意识到,许多这些消息和错误处理例程是重复的、不匹配的,或者最糟糕的是,与用户遇到的困难不再相关。此时,要找出所有这些“消息”并确保它们有意义、没有拼写错误等等,就会变得非常繁琐。
更复杂的问题是,您可能需要将应用程序分发给多种语言的用户——这正式称为本地化。对于, say,英文用户来说,使用一个英文版应用程序却收到一条荷兰语消息,这至少会让人分心,而且常常很滑稽。
如果您参与过任何比简单的“Hello World”示例更复杂的项目,您无疑已经遇到过这种情况。幸运的是,有一些工具可以解决无休止的‘MessageBox’场景。
此时,有些人可能在想,“我知道这是怎么回事”,而且您说对了。那就是使用经常被忽视的消息编译器和 Windows API 函数 FormatMessage
。然而,还有一点是本文的主题——一个易于使用的类,可以检索和显示消息。
首先,我应该提到 AMX 与可选的命名空间声明有关(通过编译器指令可选)。该类本身称为 CAppMsg
,除了简化提取和格式化消息的任务外,它还提供了一个动态对话框,是对 API MessageBox
函数显示的標準對話框的增強。
但在描述 CAppMsg
类之前,有必要简要介绍一下 Microsoft 消息编译器。
消息编译器
关于消息文件和 Windows 消息编译器 (mc.exe) 的使用,有大量信息可供参考。Visual Studio 的在线帮助是一个不错的起点。因此,我不会详细介绍消息文件的布局。可以说,消息文件为应用程序的消息提供了一个单一的控制点。这使得开发人员能够协调并依赖公认的用户信息标准。随着工作的进展,消息库(即文件)可以被编辑、重新编译(使用消息编译器),并将生成的头文件、资源文件和二进制文件分发给所有相关人员。
此外,消息文件可以轻松地配置为支持多种语言,使得本地化比滚动浏览数兆字节的代码来查找每个“消息框”实例要容易得多,出错的可能性也更小。
首先,创建一个消息文件,它只是一个文本文件(通常使用扩展名 .mc),包含一个头节,后跟消息定义。头节定义了严重性 (Severity) 和设施 (Facility) 代码以及目标语言。消息节包含应用程序消息的定义。每条消息由 MessageId、Severity、Facility、SymbolicName 和 Language 标识符以及消息内容组成。每条消息之间用一个句点 (.) 字符分隔。以下部分示例摘自 Visual Studio 文档
; // ***** Sample.mc *****
; // This is the header section.
MessageIdTypedef=DWORD
SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS
Informational=0x1:STATUS_SEVERITY_INFORMATIONAL
Warning=0x2:STATUS_SEVERITY_WARNING
Error=0x3:STATUS_SEVERITY_ERROR
)
FacilityNames=(System=0x0:FACILITY_SYSTEM
Runtime=0x2:FACILITY_RUNTIME
Stubs=0x3:FACILITY_STUBS
Io=0x4:FACILITY_IO_ERROR_CODE
)
LanguageNames=(English=0x409:MSG00409)
LanguageNames=(Japanese=0x411:MSG00411)
; // The following are message definitions.
MessageId=0x1
Severity=Error
Facility=Runtime
SymbolicName=MSG_BAD_COMMAND
Language=English
You have chosen an incorrect command.
.
定义好消息文件后,它会被消息编译器处理,如果成功,其输出将是与消息文件名同名的头文件 (.h) 和资源文件 (.rc)。此外,还会生成一个或多个二进制文件 (.bin),命名约定为 MSG00409.bin(英语)、MSG00411.bin(日语)等。二进制文件包含每种相应语言的实际消息。使用消息编译器的语法是
mc.exe AppMessages.mc
其中 AppMessages 是您特定的消息文件名。处理后,生成的这些文件将被合并到您自己的应用程序中。为此,请在应用程序的 stdafx.h 文件中添加对 AppMessages.h 头文件的引用。
#include <afxwin.h> // MFC core and standard components
#include <afxext.h> // MFC extensions
#include <afxdisp.h> // MFC Automation classes
#include <amx.h> // Support for AMX
#include <AppMessages.h> // Application’s message file header
第二个要求是在您的应用程序中放置对生成资源文件的引用。这通常在 .rc2 文件中完成。
//
// MsgDemo.RC2 - resources Microsoft Visual C++
// does not edit directly
//
#ifdef APSTUDIO_INVOKED
#error this file is not editable by Microsoft Visual C++
#endif //APSTUDIO_INVOKED
///////////////////////////////////////////////////////////////
// Add manually edited resources here...
#include "AppMessages.rc"
///////////////////////////////////////////////////////////////
同样,读者应参考 Visual Studio 文档和其他资源,以获取关于消息文件的完整讨论。
预构建事件
为了避免每次必须处理消息文件时手动调用消息编译器,可以在项目中添加一个预构建事件。要做到这一点,请转到应用程序的属性对话框并进行如下所示的条目。
CAppMsg 类
在创建、编译和合并您的消息后,可以使用 Windows API 函数 FormatMessage
来提取包含的任何消息。使用 FormatMessage
函数大致如下
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_HMODULE |
FORMAT_MESSAGE_ARGUMENT_ARRAY |
MAX_MESSAGE_LENGTH,
m_hInstance, dwMsgCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&msgBuf,
0, &va_arg(lst, char*));
有点丑陋,不是吗!在我看来,这就是为什么使用消息框语句更受欢迎,而更充分的理由是将此类函数封装在一个方便的类中,该类可以使用起来与消息框语句一样简单。
正如您可能已经体验到的,调用 FormatMessage
的原因是为您在 lpMsgBuf
变量中提供一个格式化的消息字符串。然后,该字符串用于消息框对话框。哦,等等!这正是我们一直试图摆脱的“消息框”问题!
因此,目标是拥有一个对象,该对象可以轻松地放置在任何需要的地方,只需最少的麻烦。这意味着,最少的编码工作,并避免为显示目的定义或复制对话框资源。
CAppMsg
动态创建一个对话框,因此不需要定义对话框资源,也不依赖 MFC。所有代码都是纯 C++ 和系统 API 调用。因此,除了 Visual Studio Express 版(因为它使用了标准模板库 (STL))之外,代码应该(只需极少的更改)在任何场景下都能编译。
CAppMsg
可以使用以下三种构造函数之一进行实例化
CAppMsg()
默认构造函数(调用
ExtractMessage
来检索消息)。CAppMsg(DWORD dwMsgCode, ...)
dwMsgCode
消息标识符(符号名称),来自消息编译器生成的头文件。
- 省略号 (
…
)省略号 (...) 参数是可选的,允许将额外的字符串信息格式化到应用程序消息中。
CAppMsg(LPCTSTR lpszMsg, int nSeverity = SEV_INFORMATIONAL)
lpszMsg
要显示的即时消息字符串。
nSeverity
表示将在
CAppMsg
对话框中显示的图标的枚举值。可识别的值是SEV_NONE
不显示图标。
SEV_INFORMATIONAL
信息图标。
SEV_QUESTION
问号图标。
SEV_WARNING
警告图标。
SEV_ERROR
错误图标。
除了构造函数,还有两个公共方法和一个用于异常处理的全局函数。
BOOL ExtractMessage(DWORD dwMsgCode, ...)
dwMsgCode
消息标识符(符号名称),来自消息编译器生成的头文件。
- 省略号 (
…
)省略号 (...) 参数是可选的,允许将额外的字符串信息格式化到应用程序消息中。
int ShowMessage(eDlgStyle eStyle = FD_OK, BOOL bMore = FALSE, HWND hParent = NULL)
eStyle
定义将显示哪些响应按钮
FD_OK
确定按钮
FD_OKCANCEL
确定和取消按钮
FD_YESNO
是和否按钮
FD_YESALL
是、否和全部是按钮
bMore
确定是否显示“更多…”按钮。这主要用于启动附加的帮助信息,例如已编译的 Windows 帮助文件。
hParent
父窗口的句柄。通常为
NULL
或this->m_hWnd
。
void ThrowAppException(DWORD dwErrorCode, ...)
dwErrorCode
消息标识符(符号名称),来自消息编译器生成的头文件。
- 省略号 (
…
)省略号 (...) 参数是可选的,允许将额外的字符串信息格式化到应用程序消息中。
应注意的是,CAppMsg
定义了一个在异常处理中使用的智能指针。这消除了记住删除 CAppMsg
对象的需求(从而避免了潜在的内存泄漏)。例如
try
{
//Some error occurred here!
ThrowAppException(E_SOME_ERROR);
}
catch ( CAppMsgPtr pMsg )
{
pMsg->ShowMessage(FD_OK, TRUE));
//CAppMsgPtr object is a smart pointer. No need to ‘delete’
}
使用 AMX
- 将 amx.h 和 amx.cpp 文件复制到您的项目文件夹。
- 将这些文件包含在您的项目中。
- 在 stdafx.h 文件中添加对 amx.h 的引用(通常)。
- 创建
CAppMessage
类的实例。 - 调用
ShowMessage()
进行显示。
CAppMsg
和包含的示例是使用 Visual Studio 2005 编写的,并演示了该类的几种用法。
摘要
通过结合消息文件和 CAppMsg
类,可以轻松管理健壮应用程序所需的所有消息和用户反馈信息。通过一些前期规划和协调,就可以避免“消息框”的泥潭。
演示项目还包含一个 Windows 帮助文件 (AppMsg.chm),可用作快速参考。
历史
- 2009 年 7 月 20 日 - v1.0:初始发布。
- 2009 年 8 月 12 日 - v1.1:修复了内存泄漏。