Defining Custom Error Types

Drivers can specify their own error types and error messages. To define a custom error message, you must first define a new IO_ERR_XXX value to specify as the ErrorCode member of the error log entry. The Event Viewer uses the IO_ERR_XXX value to look up the driver's error message.

To support custom error messages in your driver, follow these steps:

  1. Create a message text file that specifies the custom IO_ERR_XXX value and the corresponding error messages. For further information, see Creating the Error Message Text File.

  2. Compile the error message text file to a resource, and attach the resource to the driver image. For further information, see Compiling the Error Message Text File.

  3. Register the driver image as containing error messages. For further information, see Registering as a Source of Error Messages.

Creating the Error Message Text File

The definition of a driver's custom IO_ERR_XXX values and matching error message templates are attached as a message table resource to the driver image. You can describe the messages for a driver in a message text file (which has an .mc file name extension).

A message text file consists of two sections: a header section and a message section. The header section permits the declaration of symbolic names for numerical values, while the message section specifies the IO_ERR_XXX values and matching error message templates.

For an example of a message text file, see the Serlog.mc file in the Serial driver sample available on GitHub.

Header Section

The header section must contain this line:

MessageIdTypedef=NTSTATUS

This ensures that the type of IO_ERR_XXX values generated by the Message Compiler is declared to be NTSTATUS.

The other directives that appear in the header section define symbolic values that are used in place of numeric values in the message section.

The SeverityNames and FacilityNames directives define symbolic values for the severity and facility fields of NTSTATUS values. The directives are of the form keyword= ( values ), where values consists of one or more statements of the form name = value : header_name, separated by white space. The name parameter is the name you use the specify the numeric value in the message text file, while the header_name is the name for this value declared in the C header file generated by the Message Compiler. The : header_name clause is optional.

Here is an example of a header declaration of symbolic names for severity codes:

SeverityNames = (
  Success       = 0x0:STATUS_SEVERITY_SUCCESS
  Informational = 0x1:STATUS_SEVERITY_INFORMATIONAL
  Warning       = 0x2:STATUS_SEVERITY_WARNING
  Error         = 0x3:STATUS_SEVERITY_ERROR
)

The LanguageNames directive defines symbolic values for locale IDs (LCID). The directive is of the form LanguageNames = ( values ), where values consists of one or more statements of the form language_name = lcid : langfile, separated by white space. The language_name parameter is the name you use in place of lcid in the message text file, while the filename specifies a unique file name (without extension). When the Message Compiler generates the resource script from the message text file, it stores all of the string resources for this language in a file named langfile.bin.

Message Section

Each message definition begins with the definition of the custom IO_ERR_XXX value that the driver uses to report this particular type of error. The IO_ERR_XXX value is defined by a series of keyword = value pairs. The possible keywords and their meaning are as follows.

Keyword Value

MessageId

Code field of the new IO_ERR_XXX value.

Severity

Severity field of the new IO_ERR_XXX value. The specified value must be one of the symbolic names defined by the SeverityNames header directive.

Facility

Facility field of the new IO_ERR_XXX value. The specified value must be one of the symbolic names defined by the FacilityNames header directive.

SymbolicName

The symbolic name for the new IO_ERR_XXX value. The Message Compiler generates a C header file that contains a #define declaration of the name as the corresponding NTSTATUS value. The driver uses that name when specifying the error type.

The first keyword must always be MessageId.

The rest of the message definition consists of one or more localized versions of the error message. Each version is of the form:

Language=language_name
localized_message

The language_name value, which must be one of the symbolic names defined by the LanguageNames header directive, specifies the language of the message text. The localized message text itself consists of a Unicode string. Any embedded strings of the form "%n" will be treated as templates that the Event Viewer will replace when the error is logged. The "%1" string is replaced with the name of the driver's device object, while "%2" through "%n" are replaced with any insertion strings provided by the driver.

The message definition is terminated by a single period alone on a line.

If you define custom error messages, you should not use insertion strings unless necessary. Insertion strings cannot be localized, so they should be used for strings that are language-independent, such as numbers or file names. Most drivers do not use insertion strings.

Compiling the Error Message Text File

Use the Message Compiler (mc.exe) to compile your message text file into a resource script file (which has an .rc file name extension). A command of the form

mc filename.mc

causes the Message Compiler to generate the following files:

  • filename.h, a header file that contains declarations of each custom IO_ERR_XXX value in filename.mc.

  • filename.rc, a resource script.

  • One file for each language that appears in the message text file. Each of these files stores all of the error message string resources for one language. The file for each language is named langfile.bin, where langfile is the value specified for the language in the message text file's LanguageNames directive.

More information about the Message Compiler can be found in the Microsoft Windows SDK.

The Resource Compiler converts a resource script to a resource file that you can attach to your driver image. If you use the Build utility to build your driver, you can make sure that the resource script is converted to a resource file and attached to your driver image simply by including the name of the resource script in the SOURCES variable for the driver. For more information about the Resource Compiler, see the Windows SDK documentation. For information about using the Build utility to build your driver, see Building a Driver.