1. Windows错误代码域
对于Windows错误码,WinError.h文件中定义了有关错误代码的规则,每个DWORD值被划分成了几个部分,如下:
位 | 31-30 | 29 | 28 | 27-16 | 15-0 |
---|---|---|---|---|---|
内容 | 严重性 | Microsoft / 客户 | 保留 | 设备代码 | 异常代码 |
含义 | 0 = 成功 1 = 信息(提示) 2 = 警告 3 = 错误 |
0 = Microsoft定义的错误码 1 = 客户定义的错误码 |
必须为零 | 前256个值由Microsoft保留 | Microsoft/客户定义的代码 |
2. 编写错误信息文本文件
应用程序可以根据业务需求按照Windows错误代码域的规则定义自己的错误码, 自定义错误码需要先编写一个错误信息文本文件。文件以.mc为后缀,其语法相对来说比较简单,该文件主要包括三部分,注释、信息头(Header Section)和信息体(Message Section)。
-
注释
注释是以分号(;)开头的行,在编译后生成的C/C++头文件中,MC编译器会去掉这些分号,也就是说,如果你要生成一些带C/C++的注释,在分号后再加入C/C++的注释即可,如:
;#ifndef _YOUR_MESSAGE_ERROR_TEXT_
;#define _YOUR_MESSAGE_ERROR_TEXT_
;// C/C++单行注释
;
;/*
;
; C/C++块注释
;
;*/
;#endif
-
信息头部(Header Section)
信息头部定义信息体中需要使用的一些名称和语言标识,可以包含以下0个或多个语法声明。
语法 | 说明 |
---|---|
MessageIdTypedef=type | 声明错误码类型,该定义一般放到信息头部最前面,type在消息头文件(.h)中被使用。定义的类型(32位)必须要能够容纳所有的错误码。 |
SeverityNames=(name=number[:SymbolName]) | 声明错误等级集合,在30-31位中定义。可以定义多个错误等级,以空格分隔。默认定义为: SeverityNames=(Success=0x0 Informational=0x1 Warning=0x2 Error=0x3) ;name是错误等级名称,是在信息体中引用的名字。SymbolName是自定义的符号名,该符号编译后会在头文件中定义,具体见实例。 |
FacilityNames=(name=number[:SymbolName]) | 声明设备代码集合,在16-27位中定义,可以定义多个设备代码,中间以空格分隔。如果29为没有标记为1,则前256位系统保留使用,应用可以在0x100-0xFFF中定义。name是设备代码名称,是在信息体中引用的名字。SymbolName是自定义的符号名,该符号编译后会在头文件中定义,具体见实例。 |
LanguageNames=(name=number:filename) | 声明语言集合,其中name语句名称,是在信息体中需要引用的名字,可以支持多种语言,中间以空格分隔。number是语言标识,filename是包含对应语言的文件名,不带后缀,由MC编译器生成一个指定文件名加.bin的文件,用于存储对应语言的错误文本信息。语言标识(language identifier)由一个16bit的数组成,其中高6位是次语言标识(SubLanguage ID), 低10位是主语言标识(Primary Language ID)。关于语言标识的常量定义在这里。比如我要定义简体中文,主语言标识为0x04,次语言标识为0x02,故简体中文的语言标识为0x804,于是可以这样定义:LanguageNames=(Chinese=0x804:MSG00804)
|
-
错误信息体(Message Section)
信息体在信息头之后定义,下表是一个信息体可能包含的一些定义,每个信息体以MessageId开头,以单独成行的句点结束。Severity和Facility可选。
语法 | 说明 |
---|---|
MessageId=[number|+number] | 消息标识,必须要定义,但其值是可选的,如果没有定义值,将使用上一个消息id加1。如果使用了+号,消息标识为在上一个消息id上加指定的number。所定义的消息标识大小不能超过16位; |
Severity=name | 错误等级,如果Severity=Error; |
Facility=name | 设备代码,如Facility=Application; |
SymbolicName=name | 符号名称,该名称会被定义在C/C++头文件中,格式如:#define name ((type)0xnnnnnnnn) ;其中type在信息头MessageIdTypedef中声明; |
Language=name | 信息语言,语言名称在信息头部定义,如果没定义默认为English。 如:Language=Chinese ; |
message text | 信息对应语言的文本; |
. | 以一个独立成行的句点作为信息体结束符; |
注:如果一个信息体支持多种语言,对每一种语言都需要一个语言声明、语言文本和独立成行的句点,如:
MessageId=0x1
Severity=Error
Facility=Runtime
SymbolicName=MSG_BAD_COMMAND
Language=English
You have chosen an incorrect command.
.
Language=Chinese
<Chinese message string goes here>
.
- 完整例子
下面是错误文本文件MessageTable.mc中的内容:
;#ifndef _ERROR_MESSAGE_TEXT_
;#define _ERROR_MESSAGE_TEXT_
;
MessageIdTypedef=HRESULT
SeverityNames=(
Succes=0x0:TEST_SEVERITY_SUCESS
Informational=0x1:ITEST_SEVERITY_INFORMATIONAL
Warning=0x2:ITEST_SEVERITY_WARNING
Error=0x3:TEST_SEVERITY_ERROR
)
FacilityNames=(
TestFacility1=0x100:FACILITY_TEST_1
TestFacility2=0x101:FACILITY_TEST_2
TestFacility3=0x101:FACILITY_TEST_3
)
LanguageNames=(
English=0x0409:MSG00409
Chinese=0x0804:MSG00804
)
MessageId=0x1 Severity=Error Facility=TestFacility1 SymbolicName=TEST_E_01
Language=English
Define english string error message for TEST_E_01
.
Language=Chinese
在这儿定义TEST_E_01的中文错误文本
.
MessageId=0x2 Severity=Error Facility=TestFacility1 SymbolicName=TEST_E_02
Language=English
Define english string error message for TEST_E_02
.
Language=Chinese
在这儿定义TEST_E_02的中文错误文本
.
MessageId=0x3 Severity=Error Facility=TestFacility1 SymbolicName=TEST_E_03
Language=English
Define english string error message for TEST_E_03
.
Language=Chinese
在这儿定义TEST_E_03的中文错误文本
.
;#endif
3. Message Complier(MC.exe)
编写好错误信息文本文件之后需要用Message Compiler(MC.exe)编译工具进行编译,mc的具体用法见这里。由于我们需要支持中文,所以需要指定-u参数,另外,如果我们想为设置29位为1,需要指定-c参数,如:
mc.exe -cu MessageTable.mc
编译成功后会生成四个文件:MessageTable.h、MessageTable.rc、MSG00409.bin、MSG00804.bin
其中MessageTable.h
的内容如下:
#ifndef _ERROR_MESSAGE_TEXT_
#define _ERROR_MESSAGE_TEXT_
//
// Values are 32 bit values laid out as follows:
//
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +---+-+-+-----------------------+-------------------------------+
// |Sev|C|R| Facility | Code |
// +---+-+-+-----------------------+-------------------------------+
//
// where
//
// Sev - is the severity code
//
// 00 - Success
// 01 - Informational
// 10 - Warning
// 11 - Error
//
// C - is the Customer code flag
//
// R - is a reserved bit
//
// Facility - is the facility code
//
// Code - is the facility's status code
//
//
// Define the facility codes
//
#define FACILITY_TEST_3 0x101
#define FACILITY_TEST_2 0x101
#define FACILITY_TEST_1 0x100
//
// Define the severity codes
//
#define ITEST_SEVERITY_WARNING 0x2
#define TEST_SEVERITY_SUCESS 0x0
#define ITEST_SEVERITY_INFORMATIONAL 0x1
#define TEST_SEVERITY_ERROR 0x3
//
// MessageId: TEST_E_01
//
// MessageText:
//
// Define english string error message for TEST_E_01
//
#define TEST_E_01 ((HRESULT)0xE1000001L)
//
// MessageId: TEST_E_02
//
// MessageText:
//
// Define english string error message for TEST_E_02
//
#define TEST_E_02 ((HRESULT)0xE1000002L)
//
// MessageId: TEST_E_03
//
// MessageText:
//
// Define english string error message for TEST_E_03
//
#define TEST_E_03 ((HRESULT)0xE1000003L)
#endif
注意,在生成的头文件中发现没有错误信息的中文文本说明,由于生成的头文件中只展示第一个定义的语言文本说明,如果想展示中文说明的话,在信息体把中文定义放在第一位就行了。如:
MessageId=0x1 Severity=Error Facility=TestFacility1 SymbolicName=TEST_E_01
Language=Chinese
在这儿定义TEST_E_01的中文错误文本
.
Language=English
Define english string error message for TEST_E_01
.
4. 总结
Windows的这套错误码定义规则比较复杂,对于一些小的工程或程序库,根本不需要按照这种方式去定义错误码。但从另一个方面来看,由于Windows的错误码定义也是按照这套标准制定的,所以了解其错误码定义的原理,有助于处理Windows函数的异常情况,写出更健壮的代码。
5. 更多参数
[1] https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/defining-custom-error-types
[2] https://msdn.microsoft.com/en-us/library/windows/desktop/dd996906(v=vs.85).aspx
[3] https://msdn.microsoft.com/en-us/library/dd318693(VS.85).aspx#NotesPrim
[4] https://msdn.microsoft.com/en-us/library/dd318691(v=vs.85).aspx
[5] https://msdn.microsoft.com/en-us/library/windows/desktop/aa385638(v=vs.85).aspx