Обозначенный механизм получения строкового сообщения об ошибках можно использовать в любых приложениях или программных библиотеках. Каждый EXE и DLL может содержать свой собственный набор ресурсов, определяющий локализованные варианты сообщений об ошибках. В своём программном коде можно использовать системные коды ошибок, уже определённые в составе WinAPI, в дополнение к своим кодам (дабы повторно не определять то, что уже и так имеется в системе). Для этого при вызове функции FormatMessage используется комбинация флагов FORMAT_MESSAGE_FROM_SYSTEM и FORMAT_MESSAGE_FROM_HMODULE (см. ниже комментарии в коде). Эта комбинация сообщает, что если информация об указанном коде ошибки не будет найдена в нашем модуле (DLL или EXE), то следует выполнить повторный поиск, но уже в системных ресурсах операционной системы. Т.о. в своих ресурсах можно ограничиться определением лишь того, что отсутствует в системных ресурсах. Коды системных ошибок определены в заголовке WinError.h.
Внимание!
Обозначенная выше комбинация флагов требует, чтобы в вашем модуле присутствовал ресурс с текстовыми сообщениями об ошибках. В случае его отсутствия функция FormatMessage сгенерирует ошибку с сообщением о том, что в указанном модуле отсутствуют ресурсы. Если вы используете только системные коды ошибок, то указывать флаг FORMAT_MESSAGE_FROM_HMODULE не нужно.
Формат описания текстовых файлов, содержащих локализованные сообщения об ошибках опубликован в MSDN здесь. Пример такого файла так же присутствует в MSDN. На основе сформированного текстового файла, при помощи утилиты MC.EXE генерируется набор файлов, необходимых для встраивания (в наш EXE или DLL файл) ресурсов, содержащих локализованные сообщения об ошибках.
Пример
Предположим, что в своих функциях мы хотим использовать обозначенный механизм для сообщения об ошибках. При этом хотим использовать преимущественно системные коды ошибок, но для ряда случаем определить и свои, т.к. в системных не нашли подходящего сообщения. Кроме того, мы хотим реализовать две локализации этих ресурсов: английскую и русскую.
Примечание
Порядок поиска локализованных ресурсов достаточно ясно изложен в описании параметра dwLanguageId функции FormatMessage.
В каталоге проекта создаём новый текстовый файл в кодировке ANSI или Unicode (т.е. UTF-16, не путать с UTF-8). Если используется Notepad++, то кодировка Unicode там обозначена как UCS-2 LE BOM.
Заполняем файл содержимым, например:
Последней строкой текстового файла, содержащего локализованные сообщения об ошибках, обязательно должна быть пустая строка, иначе MC.EXE будет ругаться на инвалидный контент.
В меню Все Программы -> Visual Studio 2013 -> Visual Studio Tools открываем консоль Visual Studio: VS2013 x64 Cross Tools Command Prompt. Переходим в каталог проекта и запускаем программу MC.EXE с соответствующим набором опций:
- Если содержимое текстового файла использует кодировку ANSI:
mc.exe -a sample.mc - Если содержимое текстового файла использует кодировку UTF-16:
mc.exe -u sample.mc
В результате работы обозначенной выше команды, в каталоге проекта появятся новые файлы:
- MSG00409.bin
- MSG00419.bin
- Sample.h
- Sample.rc
Добавляем в наш проект заголовочный файл Sample.h и ресурсный файл Sample.rc. Сгенерированный утилитой MC.EXE заголовочный файл выглядит следующим образом:
// ***** Sample.mc *****
// This is the header section.
// The following are message definitions.
//
// 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_SYSTEM 0x0
#define FACILITY_STUBS 0x3
#define FACILITY_RUNTIME 0x2
#define FACILITY_IO_ERROR_CODE 0x4
//
// Define the severity codes
//
#define STATUS_SEVERITY_WARNING 0x2
#define STATUS_SEVERITY_SUCCESS 0x0
#define STATUS_SEVERITY_INFORMATIONAL 0x1
#define STATUS_SEVERITY_ERROR 0x3
//
// MessageId: MSG_BAD_COMMAND
//
// MessageText:
//
// You have chosen an incorrect command.
//
#define MSG_BAD_COMMAND ((DWORD)0xC0020001L)
//
// MessageId: MSG_BAD_PARM1
//
// MessageText:
//
// Cannot reconnect to the server.
//
#define MSG_BAD_PARM1 ((DWORD)0x80040002L)
//
// MessageId: MSG_STRIKE_ANY_KEY
//
// MessageText:
//
// Press any key to continue . . . %0
//
#define MSG_STRIKE_ANY_KEY ((DWORD)0x00000003L)
#include<Windows.h>
#include <iostream>
#include "Sample.h"
using namespace std;
/*
MSDN resources:
FormatMessage function:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351%28v=vs.85%29.aspx
Message Text Files:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd996906%28v=vs.85%29.aspx
Sample Message Text File:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd996907%28v=vs.85%29.aspx
Message Compiler (MC.exe):
https://msdn.microsoft.com/en-us/library/windows/desktop/aa385638%28v=vs.85%29.aspx
*/
// Our function uses the WinAPI mechanism for notifying of error
BOOL isValidCommandIndex(int index){
if (index < 0){
SetLastError(MSG_BAD_COMMAND); // the identifier from the Sample.h
return FALSE;
}
else{
SetLastError(ERROR_SUCCESS);
return TRUE;
}
}
int wmain(int argc, TCHAR* argv[])
{
// Getting the readable Cyrillic chars in the console window for ANSI and
// Unicode encodings...
setlocale(LC_ALL, "Russian");
int command_index = -1;
BOOL result = isValidCommandIndex(command_index);
if (!result){
DWORD errCode = GetLastError(); // Get the last error at once
LCID langId = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);
// Also you can try such variants, for getting the messages with other
// localizations:
// LCID langId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
// LCID langId = MAKELANGID(LANG_RUSSIAN, SUBLANG_RUSSIAN_RUSSIA);
PTCHAR message = NULL;
// HANDLE of EXE or DLL which contains the resource with the error messages
// which we are to get. In our case this is current EXE, therefore it is
// possible to point the NULL value of HANDLE for the FormatMessage
// function.
HANDLE handle = NULL;
int tchars_count = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | /* Search in the system resources if the
message will not be found in our module. It
allows to use the system error codes in our
code additionally to defined by us error
codes. */
FORMAT_MESSAGE_FROM_HMODULE |
FORMAT_MESSAGE_IGNORE_INSERTS, handle, errCode, langId, (PTCHAR)&message,
0, NULL);
if (0 == tchars_count){
// In the Watch[1-4] window it is possible to get the last error code
// through the '$err,hr' (without quotes) name. Add this name into the
// Watch[1-4] window if you need.
DWORD lastErrorCode = GetLastError();
if (lastErrorCode != ERROR_SUCCESS){
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, NULL, lastErrorCode, langId,
(PTCHAR)&message, 0, NULL);
}
}
wcout << message;
LocalFree(message);
}
// *******************************************
wchar_t c;
wcout << L"Нажмите любую клавишу для выхода..." << endl;
wcin >> c;
return 0;
}