Winapi get error message

After a Windows API call, how can I get the last error message in a textual form? GetLastError() returns an integer value, not a text message.

i’ll leave this here since i will need to use it later. It’s a source for a small binary compatible tool that will work equally well in assembly, C and C++.

GetErrorMessageLib.c (compiled to GetErrorMessageLib.dll)

#include <Windows.h>

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
__declspec(dllexport)
int GetErrorMessageA(DWORD dwErrorCode, LPSTR lpResult, DWORD dwBytes)
{    
    LPSTR tmp;
    DWORD result_len;

    result_len = FormatMessageA (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPSTR)&tmp,
        0,
        NULL
    );        

    if (result_len == 0) {
        return -1;
    }

    // FormatMessage's return is 1 character too short.
    ++result_len;

    strncpy(lpResult, tmp, dwBytes);

    lpResult[dwBytes - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_len <= dwBytes) {
        return 0;
    } else {
        return result_len;
    }
}

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
__declspec(dllexport)
int GetErrorMessageW(DWORD dwErrorCode, LPWSTR lpResult, DWORD dwBytes)
{   
    LPWSTR tmp;
    DWORD nchars;
    DWORD result_bytes;

    nchars = dwBytes >> 1;

    result_bytes = 2 * FormatMessageW (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPWSTR)&tmp,
        0,
        NULL
    );    

    if (result_bytes == 0) {
        return -1;
    } 

    // FormatMessage's return is 1 character too short.
    result_bytes += 2;

    wcsncpy(lpResult, tmp, nchars);
    lpResult[nchars - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_bytes <= dwBytes) {
        return 0;
    } else {
        return result_bytes * 2;
    }
}

inline version(GetErrorMessage.h):

#ifndef GetErrorMessage_H 
#define GetErrorMessage_H 
#include <Windows.h>    

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
static inline int GetErrorMessageA(DWORD dwErrorCode, LPSTR lpResult, DWORD dwBytes)
{    
    LPSTR tmp;
    DWORD result_len;

    result_len = FormatMessageA (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPSTR)&tmp,
        0,
        NULL
    );        

    if (result_len == 0) {
        return -1;
    }

    // FormatMessage's return is 1 character too short.
    ++result_len;

    strncpy(lpResult, tmp, dwBytes);

    lpResult[dwBytes - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_len <= dwBytes) {
        return 0;
    } else {
        return result_len;
    }
}

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
static inline int GetErrorMessageW(DWORD dwErrorCode, LPWSTR lpResult, DWORD dwBytes)
{   
    LPWSTR tmp;
    DWORD nchars;
    DWORD result_bytes;

    nchars = dwBytes >> 1;

    result_bytes = 2 * FormatMessageW (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPWSTR)&tmp,
        0,
        NULL
    );    

    if (result_bytes == 0) {
        return -1;
    } 

    // FormatMessage's return is 1 character too short.
    result_bytes += 2;

    wcsncpy(lpResult, tmp, nchars);
    lpResult[nchars - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_bytes <= dwBytes) {
        return 0;
    } else {
        return result_bytes * 2;
    }
}

#endif /* GetErrorMessage_H */

dynamic usecase(assumed that error code is valid, otherwise a -1 check is needed):

#include <Windows.h>
#include <Winbase.h>
#include <assert.h>
#include <stdio.h>

int main(int argc, char **argv)
{   
    int (*GetErrorMessageA)(DWORD, LPSTR, DWORD);
    int (*GetErrorMessageW)(DWORD, LPWSTR, DWORD);
    char result1[260];
    wchar_t result2[260];

    assert(LoadLibraryA("GetErrorMessageLib.dll"));

    GetErrorMessageA = (int (*)(DWORD, LPSTR, DWORD))GetProcAddress (
        GetModuleHandle("GetErrorMessageLib.dll"),
        "GetErrorMessageA"
    );        
    GetErrorMessageW = (int (*)(DWORD, LPWSTR, DWORD))GetProcAddress (
        GetModuleHandle("GetErrorMessageLib.dll"),
        "GetErrorMessageW"
    );        

    GetErrorMessageA(33, result1, sizeof(result1));
    GetErrorMessageW(33, result2, sizeof(result2));

    puts(result1);
    _putws(result2);

    return 0;
}

regular use case(assumes error code is valid, otherwise -1 return check is needed):

#include <stdio.h>
#include "GetErrorMessage.h"
#include <stdio.h>

int main(int argc, char **argv)
{
    char result1[260];
    wchar_t result2[260];

    GetErrorMessageA(33, result1, sizeof(result1));
    puts(result1);

    GetErrorMessageW(33, result2, sizeof(result2));
    _putws(result2);

    return 0;
}

example using with assembly gnu as in MinGW32(again, assumed that error code is valid, otherwise -1 check is needed).

    .global _WinMain@16

    .section .text
_WinMain@16:
    // eax = LoadLibraryA("GetErrorMessageLib.dll")
    push $sz0
    call _LoadLibraryA@4 // stdcall, no cleanup needed

    // eax = GetProcAddress(eax, "GetErrorMessageW")
    push $sz1
    push %eax
    call _GetProcAddress@8 // stdcall, no cleanup needed

    // (*eax)(errorCode, szErrorMessage)
    push $200
    push $szErrorMessage
    push errorCode       
    call *%eax // cdecl, cleanup needed
    add $12, %esp

    push $szErrorMessage
    call __putws // cdecl, cleanup needed
    add $4, %esp

    ret $16

    .section .rodata
sz0: .asciz "GetErrorMessageLib.dll"    
sz1: .asciz "GetErrorMessageW"
errorCode: .long 33

    .section .data
szErrorMessage: .space 200

result: The process cannot access the file because another process has locked a portion of the file.

Здраствуйте!

Представляю обзор функций для получения текста ошибок из их кодов, который представлен в программе Error Lookup.

1. FormatMessage

Эта функция не такая уж и простая как может показаться (большая часть функционала в вышеупомянутой программе реализована через неё, т.к. эту функцию можно настроить на получение НЕ только системных кодов ошибок, см. пункт 2). По умолчанию функция выдаёт код системной ошибки.

Пример:
Функция получает текст ошибки из кода системных ошибок

// пример получения текста системной ошибки
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, 1337, 0, lpszBuffer, cchBuffer, NULL);

printf("Error Code: %dnError Name: %s", 1337, lpszBuffer);

Результат:

Error Code: 1337
Error Text: Идентификатор безопасности имеет неверную структуру.

2. FormatMessage + FORMAT_MESSAGE_FROM_HMODULE

При установленном флаге FORMAT_MESSAGE_FROM_HMODULE можно загрузить список ошибок из модуля (DLL) в котором находится список ошибок:

  • ntdll.dll — список ошибок NTSTATUS
  • wininet.dll — список ошибок Wininet
  • pdh.dll — список ошибок Performance Data Helper
  • … допишите в комментариях если знаете ещё

Также этот метод можно использовать и в своих проектах, нужно лишь упаковать ресурс типа message table внутрь библиотеки (спасибо ertaquo)

Пример:
В этом примере база ошибок загружается из файла

ntdll.dll

// загружаем ntdll.dll для получения текста NTSTATUS ошибки
FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, LoadLibrary("ntdll.dll"), -1072037872, 0, lpszBuffer, cchBuffer, NULL);

printf("Error Code: %dnError Name: %s", -1072037872, lpszBuffer);

Результат:

Error Code: -1072037872
Error Text: Служба журнала обнаружила попытку ошибочного выделения или освобождения зарезервированного пространства.

3. DXGetErrorString & DXGetErrorDescription

  • DXGetErrorString — функция для получения имени ошибки (например ERROR_INVALID_SID)
  • DXGetErrorDescription — функция для получения текста ошибки (например The security ID structure is invalid.)

Пример:
Пример получения ошибки DirectX:

// получение текста DirectX ошибки
printf("Error Code: %dnError Name: %snError Text: %s", 1337, DXGetErrorString((HRESULT)1337), DXGetErrorDescription((HRESULT)1337));

Результат:

Error Code: 1337
Error Name: ERROR_INVALID_SID
Error Text: The security ID structure is invalid.

4. RasGetErrorString

Эта функция для получает текст ошибки из библиотеки функций RAS

Пример:
Функция получает текст ошибки из кода RAS ошибок

// пример получения текста RAS ошибки
RasGetErrorString(633, lpszBuffer, cchBuffer);

printf("Error Code %dnError Text: %s", 633, lpszBuffer);

Результат:

Error Code: 633
Error Text: Модем или другое устройство связи уже используется или не настроено.

5. GetIpErrorString

Эта функция для получения текста ошибки из библиотеки функций IP Helper Library

Пример:
Функция получает текст ошибки

// получаем текст ошибки IP Helper Library
GetIpErrorString(12, lpszBuffer, cchBuffer);

printf("Error Code %dnError Text: %s", 12, lpszBuffer);

Результат:

Error Code: 12
Error Text: General failure.

Бонус
  • Структура Message Table (см. пункт 2)
  • Пример разбора Message Table

Ссылки на скачивание программы
  • Setup
  • Portable
  • Source (C++)

Each thread will have its own last error code. The Windows API will set the last error code on the calling thread.

You should always call the GetLastError function immediately after checking a Windows API function’s return value.

The majority of Windows API functions set the last error code when they fail. Some will also set the last error code when they succeed. There are a number of functions that do not set the last error code. Always refer to the Windows API function’s documentation.

It is unsafe to use FORMAT_MESSAGE_FROM_SYSTEM without FORMAT_MESSAGE_IGNORE_INSERTS when using the FormatMessage function to get a description of an error code.

The Windows API is provided by means of a C-callable interface. Success or failure of an API call is reported strictly through return values. Exceptions aren’t part of the documented contract (although some API implementations can raise SEH exceptions, e.g. when passing a read-only lpCommandLine argument to CreateProcess).

The documentation for each API call explicitly calls out, how errors are reported. Always consult the documentation.

Some API calls return a single failure/success flag, without any additional information (e.g. GetObject):

In addition to a failure/success return value, some API calls also set the last error on failure (e.g. CreateWindow). The documentation usually contains the following standard wording for this case:

It is vital that you call GetLastError() IMMEDIATELY. The last error code can be overwritten by any other function, so if there’s an extra function call between the function that failed and the call to GetLastError() , the return from GetLastError() will no longer be reliable. Take extra caution when dealing with C++ constructors.

Once you get an error code, you will need to interpret it. You can get a comprehensive list of error codes on MSDN, at the System Error Codes (Windows) page. Alternatively, you can look in your system header files; the file with all the error code constants is winerror.h . (If you have Microsoft’s official SDK for Windows 8 or newer, this is in the shared subfolder of the include folder.)

Notes on calling GetLastError() in other programming languages

.net languages (C#, VB, etc.)

With .net, you should not P/Invoke to GetLastError() directly. This is because the .net runtime will make other Windows API calls on the same thread behind your back. For instance, the garbage collector might call VirtualFree() if it finds enough memory that it is no longer using, and this can happen between your intended function call and your call to GetLastError() .

Instead, .net provides the Marshal.GetLastWin32Error() function, which will retrieve the last error from the last P/Invoke call that you yourself made. Use this instead of calling GetLastError() directly.

(.net does not seem to stop you from importing GetLastError() anyway; I’m not sure why.)

The various facilities provided by Go for calling DLL functions (which reside in both package syscall and package golang.org/x/sys/windows ) return three values: r1 , r2 , and err . r2 is never used; you can use the blank identifier there. r1 is the function’s return value. err is the result of calling GetLastError() but converted into a type that implements error , so you can pass it up to calling functions to handle.

Because Go does not know when to call GetLastError() and when not to, it will always return a non- nil error. Therefore, the typical Go error-handling idiom

will not work. Instead, you must check r1 , exactly as you would in C, and only use err if that indicates the function returned an error:

Error reported with additional information on failure and success

Some API calls can succeed or fail in more than one way. The APIs commonly return additional information for both successful invocations as well as errors (e.g. CreateMutex).

Error reported as HRESULT value

HRESULTs are numeric 32-bit values, where bits or bit ranges encode well-defined information. The MSB is a failure/success flag, with the remaining bits storing additional information. Failure or success can be determined using the FAILED or SUCCEEDED macros. HRESULT s are commonly used with COM, but appear in non-COM implementations as well (e.g. StringCchPrintf).

Converting an error code into a message string

GetLastError returns a numerical error code. To obtain a descriptive error message (e.g., to display to a user), you can call FormatMessage :

In C++, you can simplify the interface considerably by using the std::string class:

NOTE: These functions also work for HRESULT values. Just change the first parameter from DWORD dwErrorCode to HRESULT hResult . The rest of the code can remain unchanged.

Источник

FormatMessage function (winbase.h)

Formats a message string. The function requires a message definition as input. The message definition can come from a buffer passed into the function. It can come from a message table resource in an already-loaded module. Or the caller can ask the function to search the system’s message table resource(s) for the message definition. The function finds the message definition in a message table resource based on a message identifier and a language identifier. The function copies the formatted message text to an output buffer, processing any embedded insert sequences if requested.

Syntax

Parameters

The formatting options, and how to interpret the lpSource parameter. The low-order byte of dwFlags specifies how the function handles line breaks in the output buffer. The low-order byte can also specify the maximum width of a formatted output line.

This parameter can be one or more of the following values.

Value Meaning
FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100 The function allocates a buffer large enough to hold the formatted message, and places a pointer to the allocated buffer at the address specified by lpBuffer. The lpBuffer parameter is a pointer to an LPTSTR; you must cast the pointer to an LPTSTR (for example, (LPTSTR)&lpBuffer ). The nSize parameter specifies the minimum number of TCHARs to allocate for an output message buffer. The caller should use the LocalFree function to free the buffer when it is no longer needed.

If the length of the formatted message exceeds 128K bytes, then FormatMessage will fail and a subsequent call to GetLastError will return ERROR_MORE_DATA.

In previous versions of Windows, this value was not available for use when compiling Windows Store apps. As of WindowsВ 10 this value can be used.

Windows ServerВ 2003 and WindowsВ XP:В В

If the length of the formatted message exceeds 128K bytes, then FormatMessage will not automatically fail with an error of ERROR_MORE_DATA.

FORMAT_MESSAGE_ARGUMENT_ARRAY 0x00002000 The Arguments parameter is not a va_list structure, but is a pointer to an array of values that represent the arguments.

This flag cannot be used with 64-bit integer values. If you are using a 64-bit integer, you must use the va_list structure.

FORMAT_MESSAGE_FROM_HMODULE 0x00000800 The lpSource parameter is a module handle containing the message-table resource(s) to search. If this lpSource handle is NULL, the current process’s application image file will be searched. This flag cannot be used with FORMAT_MESSAGE_FROM_STRING.

If the module has no message table resource, the function fails with ERROR_RESOURCE_TYPE_NOT_FOUND.

FORMAT_MESSAGE_FROM_STRING 0x00000400 The lpSource parameter is a pointer to a null-terminated string that contains a message definition. The message definition may contain insert sequences, just as the message text in a message table resource may. This flag cannot be used with FORMAT_MESSAGE_FROM_HMODULE or FORMAT_MESSAGE_FROM_SYSTEM.
FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 The function should search the system message-table resource(s) for the requested message. If this flag is specified with FORMAT_MESSAGE_FROM_HMODULE, the function searches the system message table if the message is not found in the module specified by lpSource. This flag cannot be used with FORMAT_MESSAGE_FROM_STRING.

If this flag is specified, an application can pass the result of the GetLastError function to retrieve the message text for a system-defined error.

FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 Insert sequences in the message definition such as %1 are to be ignored and passed through to the output buffer unchanged. This flag is useful for fetching a message for later formatting. If this flag is set, the Arguments parameter is ignored.

В

The low-order byte of dwFlags can specify the maximum width of a formatted output line. The following are possible values of the low-order byte.

Value Meaning
There are no output line width restrictions. The function stores line breaks that are in the message definition text into the output buffer.
FORMAT_MESSAGE_MAX_WIDTH_MASK 0x000000FF The function ignores regular line breaks in the message definition text. The function stores hard-coded line breaks in the message definition text into the output buffer. The function generates no new line breaks.

В

If the low-order byte is a nonzero value other than FORMAT_MESSAGE_MAX_WIDTH_MASK, it specifies the maximum number of characters in an output line. The function ignores regular line breaks in the message definition text. The function never splits a string delimited by white space across a line break. The function stores hard-coded line breaks in the message definition text into the output buffer. Hard-coded line breaks are coded with the %n escape sequence.

[in, optional] lpSource

The location of the message definition. The type of this parameter depends upon the settings in the dwFlags parameter.

dwFlags Setting Meaning
FORMAT_MESSAGE_FROM_HMODULE 0x00000800 A handle to the module that contains the message table to search.
FORMAT_MESSAGE_FROM_STRING 0x00000400 Pointer to a string that consists of unformatted message text. It will be scanned for inserts and formatted accordingly.

В

If neither of these flags is set in dwFlags, then lpSource is ignored.

The message identifier for the requested message. This parameter is ignored if dwFlags includes FORMAT_MESSAGE_FROM_STRING.

The language identifier for the requested message. This parameter is ignored if dwFlags includes FORMAT_MESSAGE_FROM_STRING.

If you pass a specific LANGID in this parameter, FormatMessage will return a message for that LANGID only. If the function cannot find a message for that LANGID, it sets Last-Error to ERROR_RESOURCE_LANG_NOT_FOUND. If you pass in zero, FormatMessage looks for a message for LANGIDs in the following order:

  1. Language neutral
  2. Thread LANGID, based on the thread’s locale value
  3. User default LANGID, based on the user’s default locale value
  4. System default LANGID, based on the system default locale value
  5. US English

If FormatMessage does not locate a message for any of the preceding LANGIDs, it returns any language message string that is present. If that fails, it returns ERROR_RESOURCE_LANG_NOT_FOUND.

A pointer to a buffer that receives the null-terminated string that specifies the formatted message. If dwFlags includes FORMAT_MESSAGE_ALLOCATE_BUFFER, the function allocates a buffer using the LocalAlloc function, and places the pointer to the buffer at the address specified in lpBuffer.

This buffer cannot be larger than 64K bytes.

If the FORMAT_MESSAGE_ALLOCATE_BUFFER flag is not set, this parameter specifies the size of the output buffer, in TCHARs. If FORMAT_MESSAGE_ALLOCATE_BUFFER is set, this parameter specifies the minimum number of TCHARs to allocate for an output buffer.

The output buffer cannot be larger than 64K bytes.

[in, optional] Arguments

An array of values that are used as insert values in the formatted message. A %1 in the format string indicates the first value in the Arguments array; a %2 indicates the second argument; and so on.

The interpretation of each value depends on the formatting information associated with the insert in the message definition. The default is to treat each value as a pointer to a null-terminated string.

By default, the Arguments parameter is of type va_list*, which is a language- and implementation-specific data type for describing a variable number of arguments. The state of the va_list argument is undefined upon return from the function. To use the va_list again, destroy the variable argument list pointer using va_end and reinitialize it with va_start.

If you do not have a pointer of type va_list*, then specify the FORMAT_MESSAGE_ARGUMENT_ARRAY flag and pass a pointer to an array of DWORD_PTR values; those values are input to the message formatted as the insert values. Each insert must have a corresponding element in the array.

Return value

If the function succeeds, the return value is the number of TCHARs stored in the output buffer, excluding the terminating null character.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

Within the message text, several escape sequences are supported for dynamically formatting the message. These escape sequences and their meanings are shown in the following tables. All escape sequences start with the percent character (%).

Escape sequence Meaning
%0 Terminates a message text line without a trailing new line character. This escape sequence can be used to build up long lines or to terminate the message itself without a trailing new line character. It is useful for prompt messages.
%n!format string! Identifies an insert sequence. The value of n can be in the range from 1 through 99. The format string (which must be surrounded by exclamation marks) is optional and defaults to !s! if not specified. For more information, see Format Specification Fields.

The format string can include a width and precision specifier for strings and a width specifier for integers. Use an asterisk () to specify the width and precision. For example, %1!.*s! or %1!*u!.

If you do not use the width and precision specifiers, the insert numbers correspond directly to the input arguments. For example, if the source string is «%1 %2 %1» and the input arguments are «Bill» and «Bob», the formatted output string is «Bill Bob Bill».

However, if you use a width and precision specifier, the insert numbers do not correspond directly to the input arguments. For example, the insert numbers for the previous example could change to «%1!*.*s! %4 %5!*s!».

The insert numbers depend on whether you use an arguments array (FORMAT_MESSAGE_ARGUMENT_ARRAY) or a va_list. For an arguments array, the next insert number is n+2 if the previous format string contained one asterisk and is n+3 if two asterisks were specified. For a va_list, the next insert number is n+1 if the previous format string contained one asterisk and is n+2 if two asterisks were specified.

If you want to repeat «Bill», as in the previous example, the arguments must include «Bill» twice. For example, if the source string is «%1!*.*s! %4 %5!*s!», the arguments could be, 4, 2, Bill, Bob, 6, Bill (if using the FORMAT_MESSAGE_ARGUMENT_ARRAY flag). The formatted string would then be «В В Bi Bob В В Bill».

Repeating insert numbers when the source string contains width and precision specifiers may not yield the intended results. If you replaced %5 with %1, the function would try to print a string at address 6 (likely resulting in an access violation).

Floating-point format specifiers—e, E, f, and g—are not supported. The workaround is to use the StringCchPrintf function to format the floating-point number into a temporary buffer, then use that buffer as the insert string.

Inserts that use the I64 prefix are treated as two 32-bit arguments. They must be used before subsequent arguments are used. Note that it may be easier for you to use StringCchPrintf instead of this prefix.

Any other nondigit character following a percent character is formatted in the output message without the percent character. Following are some examples.

Format string Resulting output
%% A single percent sign.
%b A single space. This format string can be used to ensure the appropriate number of trailing spaces in a message text line.
%. A single period. This format string can be used to include a single period at the beginning of a line without terminating the message text definition.
%! A single exclamation point. This format string can be used to include an exclamation point immediately after an insert without its being mistaken for the beginning of a format string.
%n A hard line break when the format string occurs at the end of a line. This format string is useful when FormatMessage is supplying regular line breaks so the message fits in a certain width.
%r A hard carriage return without a trailing newline character.
%t A single tab.

В

Security Remarks

Examples

The FormatMessage function can be used to obtain error message strings for the system error codes returned by GetLastError. For an example, see Retrieving the Last-Error Code.

The following example shows how to implement the previous example using va_list.

Источник

Читайте также:  Ffmpeg error reading header

Adblock
detector

Each thread will have its own last error code. The Windows API will set the last error code on the calling thread.

You should always call the GetLastError function immediately after checking a Windows API function’s return value.

The majority of Windows API functions set the last error code when they fail. Some will also set the last error code when they succeed. There are a number of functions that do not set the last error code. Always refer to the Windows API function’s documentation.

It is unsafe to use FORMAT_MESSAGE_FROM_SYSTEM without FORMAT_MESSAGE_IGNORE_INSERTS when using the FormatMessage function to get a description of an error code.

Introduction

The Windows API is provided by means of a C-callable interface. Success or failure of an API call is reported strictly through return values. Exceptions aren’t part of the documented contract (although some API implementations can raise SEH exceptions, e.g. when passing a read-only lpCommandLine argument to CreateProcess).

Error reporting roughly falls into one of four categories:

  • Return value only
  • Return value with additional information on failure
  • Return value with additional information on failure and success
  • HRESULT return value

The documentation for each API call explicitly calls out, how errors are reported. Always consult the documentation.

Error reported by return value only

Some API calls return a single failure/success flag, without any additional information (e.g. GetObject):

if ( GetObjectW( obj, 0, NULL ) == 0 ) {
    // Failure: no additional information available.
}

Error reported with additional information on failure

In addition to a failure/success return value, some API calls also set the last error on failure (e.g. CreateWindow). The documentation usually contains the following standard wording for this case:

If the function succeeds, the return value is <API-specific success value>.
If the function fails, the return value is <API-specific error value>. To get extended error information, call GetLastError.

if ( CreateWindowW( ... ) == NULL ) {
    // Failure: get additional information.
    DWORD dwError = GetLastError();
} else {
    // Success: must not call GetLastError.
}

It is vital that you call GetLastError() IMMEDIATELY. The last error code can be overwritten by any other function, so if there’s an extra function call between the function that failed and the call to GetLastError(), the return from GetLastError() will no longer be reliable. Take extra caution when dealing with C++ constructors.

Once you get an error code, you will need to interpret it. You can get a comprehensive list of error codes on MSDN, at the System Error Codes (Windows) page. Alternatively, you can look in your system header files; the file with all the error code constants is winerror.h. (If you have Microsoft’s official SDK for Windows 8 or newer, this is in the shared subfolder of the include folder.)

Notes on calling GetLastError() in other programming languages

.net languages (C#, VB, etc.)

With .net, you should not P/Invoke to GetLastError() directly. This is because the .net runtime will make other Windows API calls on the same thread behind your back. For instance, the garbage collector might call VirtualFree() if it finds enough memory that it is no longer using, and this can happen between your intended function call and your call to GetLastError().

Instead, .net provides the Marshal.GetLastWin32Error() function, which will retrieve the last error from the last P/Invoke call that you yourself made. Use this instead of calling GetLastError() directly.

(.net does not seem to stop you from importing GetLastError() anyway; I’m not sure why.)

Go

The various facilities provided by Go for calling DLL functions (which reside in both package syscall and package golang.org/x/sys/windows) return three values: r1, r2, and err. r2 is never used; you can use the blank identifier there. r1 is the function’s return value. err is the result of calling GetLastError() but converted into a type that implements error, so you can pass it up to calling functions to handle.

Because Go does not know when to call GetLastError() and when not to, it will always return a non-nil error. Therefore, the typical Go error-handling idiom

r1, _, err := syscall.Syscall12(CreateWindowW.Addr(), ...)
if err != nil {
    // handle err
}
// use r1

will not work. Instead, you must check r1, exactly as you would in C, and only use err if that indicates the function returned an error:

r1, _, err := syscall.Syscall12(CreateWindowW.Addr(), ...)
if r1 == 0 {
    // handle err
}
// use r1

Error reported with additional information on failure and success

Some API calls can succeed or fail in more than one way. The APIs commonly return additional information for both successful invocations as well as errors (e.g. CreateMutex).

if ( CreateMutexW( NULL, TRUE, L"Global\MyNamedMutex" ) == NULL ) {
    // Failure: get additional information.
    DWORD dwError = GetLastError();
} else {
    // Success: Determine which mutex was returned.
    if ( GetLastError() == ERROR_ALREADY_EXISTS ) {
        // Existing mutex object returned.
    } else {
        // Newly created mutex object returned.
    }
}

Error reported as HRESULT value

HRESULTs are numeric 32-bit values, where bits or bit ranges encode well-defined information. The MSB is a failure/success flag, with the remaining bits storing additional information. Failure or success can be determined using the FAILED or SUCCEEDED macros. HRESULTs are commonly used with COM, but appear in non-COM implementations as well (e.g. StringCchPrintf).

const size_t cchBuf = 5;
wchar_t buffer[cchBuf] = { 0 };
HRESULT hr = StringCchPrintfW( buffer, cchBuf, L"%s", L"Hello, world!" );
if ( FAILED( hr ) ) {
    // Failure: Determine specific reason.
    switch ( hr ) {
    case STRSAFE_E_INSUFFICIENT_BUFFER:
        // Buffer too small; increase buffer and retry.
        ...
    case STRSAFE_E_INVALID_PARAMETER:
        // Invalid parameter; implement custom error handling (e.g. logging).
        ...
    default:
        // Some other error code; implement custom error handling (e.g. logging).
        ...
    }
}

Converting an error code into a message string

GetLastError returns a numerical error code. To obtain a descriptive error message (e.g., to display to a user), you can call FormatMessage:

// This functions fills a caller-defined character buffer (pBuffer)
// of max length (cchBufferLength) with the human-readable error message
// for a Win32 error code (dwErrorCode).
// 
// Returns TRUE if successful, or FALSE otherwise.
// If successful, pBuffer is guaranteed to be NUL-terminated.
// On failure, the contents of pBuffer are undefined.
BOOL GetErrorMessage(DWORD dwErrorCode, LPTSTR pBuffer, DWORD cchBufferLength)
{
    if (cchBufferLength == 0)
    {
        return FALSE;
    }

    DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                 NULL,  /* (not used with FORMAT_MESSAGE_FROM_SYSTEM) */
                                 dwErrorCode,
                                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                 pBuffer,
                                 cchBufferLength,
                                 NULL);
    return (cchMsg > 0);
}

In C++, you can simplify the interface considerably by using the std::string class:

#include <Windows.h>
#include <exception>
#include <stdexcept>
#include <memory>
#include <string>
typedef std::basic_string<TCHAR> String;

String GetErrorMessage(DWORD dwErrorCode)
{
    LPTSTR psz = NULL;
    const DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
                                         | FORMAT_MESSAGE_IGNORE_INSERTS
                                         | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                                       NULL, // (not used with FORMAT_MESSAGE_FROM_SYSTEM)
                                       dwErrorCode,
                                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                       reinterpret_cast<LPTSTR>(&psz),
                                       0,
                                       NULL);
    if (cchMsg > 0)
    {
        // Assign buffer to smart pointer with custom deleter so that memory gets released
        // in case String's c'tor throws an exception.
        auto deleter = [](void* p) { ::HeapFree(::GetProcessHeap(), 0, p); };
        std::unique_ptr<TCHAR, decltype(deleter)> ptrBuffer(psz, deleter);
        return String(ptrBuffer.get(), cchMsg);
    }
    else
    {
        throw std::runtime_error("Failed to retrieve error message string.");
    }
}

NOTE: These functions also work for HRESULT values. Just change the first parameter from DWORD dwErrorCode to HRESULT hResult. The rest of the code can remain unchanged.

замечания

Каждый поток будет иметь свой последний код ошибки. Windows API установит последний код ошибки в вызывающем потоке.

Вы всегда должны вызывать GetLastError сразу после проверки возвращаемого значения функции API Windows.

Большинство функций Windows API устанавливают последний код ошибки, когда они терпят неудачу. Некоторые также установят последний код ошибки, когда они преуспеют. Существует ряд функций, которые не устанавливают последний код ошибки. Всегда обращайтесь к документации по функциям Windows API.

Невозможно использовать FORMAT_MESSAGE_FROM_SYSTEM без FORMAT_MESSAGE_IGNORE_INSERTS при использовании функции FormatMessage для получения описания кода ошибки.

Вступление

API Windows предоставляется с помощью C-вызываемого интерфейса. Успех или сбой вызова API сообщаются строго через возвращаемые значения. Исключения не являются частью документированного контракта (хотя некоторые реализации API могут вызывать исключения SEH , например, при передаче аргумента lpCommandLine только для чтения в CreateProcess ).

Сообщение об ошибке грубо относится к одной из четырех категорий:

  • Только возвращаемое значение
  • Возвращаемое значение с дополнительной информацией о сбое
  • Возвращаемое значение с дополнительной информацией об отказе и успехе
  • Возвращаемое значение HRESULT

Документация для каждого вызова API явно вызывается, как сообщаются ошибки. Всегда обращайтесь к документации.

Ошибка, сообщенная только возвратным значением

Некоторые вызовы API возвращают единый флаг отказа / успеха без какой-либо дополнительной информации (например, GetObject ):

if ( GetObjectW( obj, 0, NULL ) == 0 ) {
    // Failure: no additional information available.
}

Сообщается об ошибке с сообщением об ошибке

В дополнение к возвращаемому значению отказа / успеха некоторые вызовы API также устанавливают последнюю ошибку при сбое (например, CreateWindow ). Документация обычно содержит следующую стандартную формулировку для этого случая:

Если функция завершается успешно, возвращаемое значение <значение успеха API> .
Если функция не работает, возвращаемое значение <значение ошибки API> . Чтобы получить расширенную информацию об ошибке, вызовите GetLastError .

if ( CreateWindowW( ... ) == NULL ) {
    // Failure: get additional information.
    DWORD dwError = GetLastError();
} else {
    // Success: must not call GetLastError.
}

Очень важно, что вы вызываете GetLastError() НЕМЕДЛЕННО. Последний код ошибки может быть перезаписан любой другой функцией, поэтому, если есть дополнительная функция вызова между неудавшейся функцией и вызовом GetLastError() , возврат из GetLastError() больше не будет надежным. Будьте особенно осторожны при работе с конструкторами C ++.

Как только вы получите код ошибки, вам нужно будет его интерпретировать. Вы можете получить полный список кодов ошибок в MSDN на странице Системные коды ошибок (Windows) . Кроме того, вы можете посмотреть в своих файлах заголовков системы; файл со всеми константами кода ошибки — winerror.h . (Если у вас есть официальный SDK от Microsoft для Windows 8 или новее, это находится в shared папке с папкой include.)

Заметки о вызове GetLastError() на других языках программирования

.net (C #, VB и т. д.)

С .net вы не должны P / Invoke в GetLastError() напрямую. Это связано с тем, что среда выполнения .net сделает другие вызовы Windows API одним и тем же потоком за вашей спиной. Например, сборщик мусора может вызвать VirtualFree() если он найдет достаточно памяти, которую он больше не использует, и это может произойти между вашим назначенным вызовом функции и вашим вызовом GetLastError() .

Вместо этого .net предоставляет Marshal.GetLastWin32Error() , которая будет извлекать последнюю ошибку из последнего вызова P / Invoke, который вы сами сделали. Используйте это вместо прямого вызова GetLastError() .

(.net, похоже, не мешает вам импортировать GetLastError() любом случае, я не уверен, почему.)

Идти

Различные средства, предоставляемые Go для вызова DLL-функций (которые находятся как в syscall пакета, syscall и в пакете golang.org/x/sys/windows ), возвращают три значения: r1 , r2 и err . r2 никогда не используется; вы можете использовать пустой идентификатор. r1 — возвращаемое значение функции. err является результатом вызова GetLastError() но преобразуется в тип, реализующий error , поэтому вы можете передать его вызывающим функциям для обработки.

Поскольку Go не знает, когда вызывать GetLastError() а когда нет, он всегда будет возвращать ошибку nil . Поэтому типичная идиома обработки ошибок Go

r1, _, err := syscall.Syscall12(CreateWindowW.Addr(), ...)
if err != nil {
    // handle err
}
// use r1

не будет работать. Вместо этого вы должны проверить r1 точно так же, как и на C, и использовать только err если это указывает, что функция возвратила ошибку:

r1, _, err := syscall.Syscall12(CreateWindowW.Addr(), ...)
if r1 == 0 {
    // handle err
}
// use r1

Сообщается об ошибке с дополнительной информацией о сбоях и успехах

Некоторые вызовы API могут преуспеть или сбой более чем одним способом. API обычно возвращают дополнительную информацию как для успешных вызовов, так и для ошибок (например, CreateMutex ).

if ( CreateMutexW( NULL, TRUE, L"Global\MyNamedMutex" ) == NULL ) {
    // Failure: get additional information.
    DWORD dwError = GetLastError();
} else {
    // Success: Determine which mutex was returned.
    if ( GetLastError() == ERROR_ALREADY_EXISTS ) {
        // Existing mutex object returned.
    } else {
        // Newly created mutex object returned.
    }
}

Ошибка, сообщенная как значение HRESULT

HRESULT s — числовые 32-битные значения, где биты или диапазоны бит кодируют четко определенную информацию. MSB — это флаг отказа / успеха, а остальные бит хранят дополнительную информацию. Отказ или успех можно определить с помощью макросов FAILED или SUCCEEDED . HRESULT s обычно используются совместно с COM, но также отображаются в реализациях, отличных от COM (например, StringCchPrintf ).

const size_t cchBuf = 5;
wchar_t buffer[cchBuf] = { 0 };
HRESULT hr = StringCchPrintfW( buffer, cchBuf, L"%s", L"Hello, world!" );
if ( FAILED( hr ) ) {
    // Failure: Determine specific reason.
    switch ( hr ) {
    case STRSAFE_E_INSUFFICIENT_BUFFER:
        // Buffer too small; increase buffer and retry.
        ...
    case STRSAFE_E_INVALID_PARAMETER:
        // Invalid parameter; implement custom error handling (e.g. logging).
        ...
    default:
        // Some other error code; implement custom error handling (e.g. logging).
        ...
    }
}

Преобразование кода ошибки в строку сообщения

GetLastError возвращает числовой код ошибки. Чтобы получить описательное сообщение об ошибке ( например , для отображения пользователю), вы можете вызвать FormatMessage :

// This functions fills a caller-defined character buffer (pBuffer)
// of max length (cchBufferLength) with the human-readable error message
// for a Win32 error code (dwErrorCode).
// 
// Returns TRUE if successful, or FALSE otherwise.
// If successful, pBuffer is guaranteed to be NUL-terminated.
// On failure, the contents of pBuffer are undefined.
BOOL GetErrorMessage(DWORD dwErrorCode, LPTSTR pBuffer, DWORD cchBufferLength)
{
    if (cchBufferLength == 0)
    {
        return FALSE;
    }

    DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                 NULL,  /* (not used with FORMAT_MESSAGE_FROM_SYSTEM) */
                                 dwErrorCode,
                                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                 pBuffer,
                                 cchBufferLength,
                                 NULL);
    return (cchMsg > 0);
}

В C ++ вы можете значительно упростить интерфейс, используя класс std::string :

#include <Windows.h>
#include <exception>
#include <stdexcept>
#include <memory>
#include <string>
typedef std::basic_string<TCHAR> String;

String GetErrorMessage(DWORD dwErrorCode)
{
    LPTSTR psz = NULL;
    const DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
                                         | FORMAT_MESSAGE_IGNORE_INSERTS
                                         | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                                       NULL, // (not used with FORMAT_MESSAGE_FROM_SYSTEM)
                                       dwErrorCode,
                                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                       reinterpret_cast<LPTSTR>(&psz),
                                       0,
                                       NULL);
    if (cchMsg > 0)
    {
        // Assign buffer to smart pointer with custom deleter so that memory gets released
        // in case String's c'tor throws an exception.
        auto deleter = [](void* p) { ::HeapFree(::GetProcessHeap(), 0, p); };
        std::unique_ptr<TCHAR, decltype(deleter)> ptrBuffer(psz, deleter);
        return String(ptrBuffer.get(), cchMsg);
    }
    else
    {
        throw std::runtime_error("Failed to retrieve error message string.");
    }
}

ПРИМЕЧАНИЕ. Эти функции также работают для значений HRESULT . Просто измените первый параметр из DWORD dwErrorCode на HRESULT hResult . Остальная часть кода может оставаться неизменной.

Опубликовано: 10 августа 2017

Исправлено: 10 августа 2017

Версия документа: 1.0

Когда ты вызываешь функцию Windows, она проверяет переданные ей параметры, а затем пытается выполнить свою работу. Если ты передал недопустимый параметр или если данную операцию нельзя выполнить по какой‐то другой причине, она возвращает значение, свидетельствующее об ошибке.

Типы данных для возвращаемых значений

Большинство функций Windows возвращают следующие типы:

Процедуры:
Подпрограммы‐процедуры Sub не возвращают значений. Такие функции всегда (или почти всегда) выполняется успешно, хотя их количество в Windows очень мало. Пример: функция ExitProcess.
BOOL или Boolean:
Если вызов функции оканчивается неудачей, то возвращается ложь False (она же 0), в остальных случаях возвращается любое другое число, отличное от нуля. Однако не пытайся сравнить это число с True, лучше просто сравнивать с нулём.
HANDLE:
Если вызов функции оканчивается неудачей, то обычно возвращается NULL, что эквивалентно нулю, в остальных случаях возвращаемое значение идентифицирует объект, которым ты можешь манипулировать. Однако некоторые функции вместо NULL в случае ошибки возвращают константу INVALID_HANDLE_VALUE, например, функция CreateFile. В документации для каждой функции чётко указано, что именно она возвращает при ошибке: NULL или INVALID_HANDLE_VALUE.
PVOID или Any Ptr:
Если вызов функции оканчивается неудачей, то возвращается NULL, в остальных случаях PVOID сообщает адрес блока данных в памяти.
HRESULT:
Если вызов функции оканчивается неудачей, то возвращается ошибочный код HRESULT, в остальных случаях значение говорит об успехе операции. Подробнее о HRESULT →
Integer, Long или DWORD:
Это значение — «крепкий орешек». Функции, которые возвращают значения каких‐либо счётчиков, обычно возвращают Integer, Long или DWORD. Если по какой‐либо причине функция не сумела сосчитать то, что ты хотел, она обычно возвращает 0 или -1, всё зависит от конкретной функции. Лучше всего проверь в документации, каким именно значением функция уведомляет об ошибке.

Почему же произошла ошибка?

При возникновении ошибки необходимо разобраться почему вызов данной функции оказался неудачен. За каждой ошибкой закреплён свой код — 32‐битное целое число.

Функция Windows, обнаружив ошибку, через механизм локальной памяти потока сопоставляет соответствующий код ошибки с вызывающим потоком. Это позволяет потокам работать независимо друг от друга, не вмешиваясь в чужие ошибки. Когда функция вернёт управление, её возвращаемое значение будет указывать на то, что произошла какая‐то ошибка. Какая именно — можно узнать, вызвав функцию GetLastError.

Declare Function GetLastError()As DWORD

Она просто возвращает числовое значение, характеризующее код ошибки.

Список кодов ошибок лежит в заголовочной файле winwinerror.bi. Здесь приведена его небольшая часть, чтобы примерно представлять, на что он похож:

Const ERROR_SUCCESS = 0
Const NO_ERROR = 0
Const ERROR_INVALID_FUNCTION = 1
Const ERROR_FILE_NOT_FOUND = 2
Const ERROR_PATH_NOT_FOUND = 3
Const ERROR_TOO_MANY_OPEN_FILES = 4
Const ERROR_ACCESS_DENIED = 5
Const ERROR_INVALID_HANDLE = 6

Функцию GetLastError необходимо вызывать сразу же после неудачного вызова функции Windows, иначе код ошибки может быть потерян.

Некоторые функции Windows всегда завершаются успешно, но по разным причинам. Например, попытка создать объект ядра «событие» с определённым именем может быть успешна потому, что оно действительно создано, либо потому, что такой объект уже существует. Но иногда нужно знать причину успеха. Для возврата этой информации корпорация Microsoft предпочла использовать механизм установки кода последней ошибки. Так что и при успешном выполнении некоторых функций ты можешь использовать GetLastError и получать дополнительную информацию. К таким функциям относится, например, CreateEvent.

Ты наверняка спросишь, составит ли корпорация Microsoft полный список всех кодов ошибок, возможных в каждой функции. Ответ: нет. Такого списка никогда не будет, уж слишком сложно его составлять и поддерживать для всё новых и новых версий системы.

Проблема с подобным списком ещё и в том, что ты вызываешь одну функцию, а она может обратиться к другой, та — к следующей и так далее. Любая из этих функций может завершиться неудачно и по самым разным причинам. Иногда функция более высокого уровня сама справляется с ошибкой в одной из вызванных ею функций, и в конечном счёте выполняет то, что от неё хотели. В общем, для создания такого списка пришлось бы проследить цепочки вызовов в каждой функции, что очень трудно. А с появлением новой версии операционной системы цепочки вызовов пришлось бы пересматривать заново.

Определение собственных кодов ошибок

Механизм установки кода ошибки можно использовать и в собственных функциях. Предположим, ты пишешь библиотечную функцию, к которой будут обращаться другие части программы или вообще другие программы. Вызов этой функции по каким‐либо причинам может оказаться неудачным и тебе придётся тоже сообщать об этом. С этой целью ты просто устанавливаешь код последней ошибки в потоке и возвращаешь значение False, INVALID_HANDLE_VALUE, NULL или что‐то другое, более подходящее в твоём случае.

SetLastError

Чтобы установить код последней ошибки вызывай функцию SetLastError и передай ей нужной число.

Declare Sub SetLastError( _
    ByVal dwErrorCode As DWORD _
)

Можно использовать коды ошибок, уже определённые в winerror.bi, если они подходят. Если ты считаешь, что ни один из кодов ошибок из winerror.bi не годится для ошибки, возможной в твоей функции, можно определить свой код.

Формат кода ошибки

Код ошибки представляет 32‐битное беззнаковое число, которое разбито на поля:

Биты числа 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Описание Степень тяжести Кем определён Зарезервировано Подсистема (facility code) Код ошибки
Биты 30 и 31:
Представляют собой степень тяжести ошибки. В двоичном виде: 00 — успех, 01 — информация, 10 — предупреждение, 11 — ошибка.
Бит 29:
Корпорация Microsoft обещала, что никогда не будет его устанавливать. Следовательно, если ты определяешь собственный код ошибки, то установи этот бит в 1 для гарантии, что твой код ошибки не будет конфликтовать с кодами, определёнными Microsoft.
Бит 28:
Зарезервирован. Должен быть 0.
Биты с 16 по 27:
Код подсистемы (facility code). Определяется корпорацией Microsoft. Указывает на компонент операционной системы, вызвавший ошибку.
Биты с 0 по 15:
Код ошибки. Определяется корпорацией Microsoft или пользователем.

Подробнее об этих полях будет рассказано в следующих статьях. На данный момент единственное важное для тебя поле — это бит 29. Чтобы гарантировать непересекаемость кодов ошибок от Microsoft, установи его в 1. В переводе на числа это означает, что твой код ошибки должен быть больше, чем &h20000000 или 536870912 в десятичном виде.

Получение описания ошибки

Для получения текстового описания ошибки подойдёт функция FormatMessage. Использовать её можно так:

#include "windows.bi"

Const BufferSize As Integer = 4096 - 1

' Строка с ошибкой
Dim ErrorMessage As WString * (BufferSize + 1) = Any

' Вызов функции с неправильным параметром
GetProcessId(NULL)

' Получить код ошибки
Dim dwError As DWORD = GetLastError()

' Получить строку по коду ошибки
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM + FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), @ErrorMessage, BufferSize, NULL)

' Выводим описание ошибки на консоль
Print ErrorMessage

В WinAPI многие функции, в случае неудачного завершения своей работы, возвращают код ошибки, получить который можно при помощи функции GetLastError(). Само по себе полученное числовое значение не даёт чёткого представления о причине сбоя. Для того, чтобы понять, что же именно произошло, необходимо получить строковое сообщение, соответствующее возвращённому коду ошибки. В WinAPI существует функция FormatMessage которая, помимо заложенных в ней возможностей касающихся форматирования строк, может использоваться для получения текстового описания ошибки по её коду. Однако, использовать обозначенный механизм получения описаний ошибок можно не только для кодов системных ошибок, но так же и для кодов ошибок, определяемых вами…

Обозначенный механизм получения строкового сообщения об ошибках можно использовать в любых приложениях или программных библиотеках. Каждый 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.

Заполняем файл содержимым, например:

; // ***** 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=(Russian=0x419:MSG00419)

; // The following are message definitions.

MessageId=0x1

Severity=Error

Facility=Runtime

SymbolicName=MSG_BAD_COMMAND

Language=English

You have chosen an incorrect command.

.

Language=Russian

Вы выбрали неправильную команду.

.

MessageId=0x2

Severity=Warning

Facility=Io

SymbolicName=MSG_BAD_PARM1

Language=English

Cannot reconnect to the server.

.

Language=Russian

Не удаётся подключиться к серверу.

.

MessageId=0x3

Severity=Success

Facility=System

SymbolicName=MSG_STRIKE_ANY_KEY

Language=English

Press any key to continue . . . %0

.

Language=Russian

Нажмите любую клавишу для продолжения . . . %0

.

Внимание!
Последней строкой текстового файла, содержащего локализованные сообщения об ошибках, обязательно должна быть пустая строка, иначе 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)
 

Подключаем заголовочный файл Sample.h в коде исходников и используем его. В обозначенном ниже примере мы определяем функцию isValidCommandIndex, использующую «родной» механизм WinAPI для оповещения о возникновении ошибок:

#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;
}

Компилируем наш код и запускаем его. В консоли получаем следующий вывод:

Вы выбрали неправильную команду.

Нажмите любую клавишу для выхода…

Если указать английскую локализацию (см. комментарии в коде), то сообщение об ошибке будет на английском:

You have chosen an incorrect command.

Нажмите любую клавишу для выхода…

Как видим, текст сообщения об ошибке соответствует тому, который мы определили в составе ресурсов нашего EXE файла.

Где это может пригодиться?

Например, если созданные вами функции могут использоваться сторонними разработчиками, имеющими опыт работы сWinAPI, то задействование стандартного механизма оповещения об ошибках, предоставляемого операционной системой Windows, для них будет делом привычным. Вам достаточно будет лишь сообщить в документации о том, что для получения кода ошибки и его расшифровки следует использовать стандартный механизм WinAPI: функции GetLastError и FormatMessage.

Функция FormatMessage


Форматирует строку сообщения. Функция требует определения сообщения как вводимых данных. Определение сообщения может прийти из буфера, который передается в функцию. Оно может прийти из ресурса таблицы сообщений в уже
загруженном модуле. Или вызывающая программа (вызывающий модуль) может попросить, чтобы функция нашла в ресурсе (ах) таблицы системных сообщений определение сообщения. Функция находит определение сообщения в ресурсе
таблицы сообщений основанных на идентификаторе сообщения и идентификаторе языка. Функция копирует форматированный текст сообщения в буфер выводимых данных, обрабатывая любые встроенные последовательности вставок, если это
требуется.

Синтаксис

 

Параметры

dwFlags

Варианты форматирования и как интерпретировать параметр lpSource. Младший байт
dwFlags определяет, как функция обрабатывает разрывы строк в буфере выводимых данных. Младший байт может также установить максимальную ширину
выводимой форматированной строки.

Этот параметр, может иметь один или несколько нижеследующих значений.

Значение

Предназначение

FORMAT_MESSAGE_ALLOCATE_BUFFER
0x00000100
Параметр lpBuffer — указатель на указатель
PVOID, и что параметр nSize определяет минимальное число
TCHARs, чтобы назначить для буфера выходное сообщение. Функция назначает буфер,
достаточно большой, чтобы содержать форматированное сообщение и помещает указатель в выделенный буфер по адресу, указанному в параметре
lpBuffer. Вызывающая программа (вызывающий модуль) должна
использовать функцию LocalFree для освобождения буфера, когда он больше не нужен.
FORMAT_MESSAGE_ARGUMENT_ARRAY
0x00002000
Параметр Arguments не структура va_list, но является указателем на массив значений, которые представляют параметры.

Этот флажок не может использоваться с 64-битовым целым значением.
Если Вы используете 64-битовое целое число, Вы должны использовать структуру
va_list.

FORMAT_MESSAGE_FROM_HMODULE
0x00000800
Параметр lpSource — это дескриптор модуля, содержащего в себе искомый ресурс(ы) таблицы сообщений. Если этот дескриптор
lpSource — NULL, то нужно искать загрузочный модуль
приложения текущего процесса. Этот флажок не может использоваться с флажком
FORMAT_MESSAGE_FROM_STRING.

Если у модуля нет никакого ресурса таблицы сообщений, функция завершается ошибкой с кодом
ERROR_RESOURCE_TYPE_NOT_FOUND.

FORMAT_MESSAGE_FROM_STRING
0x00000400
Параметр lpSource — указатель на строку с завершающим нулем, который содержит определение сообщения. Определение сообщения может содержать в себе последовательности
вставок, а так же может содержать текст сообщения в ресурсе таблицы сообщений. Этот флажок не может использоваться с флажками
FORMAT_MESSAGE_FROM_HMODULE или
FORMAT_MESSAGE_FROM_SYSTEM.
FORMAT_MESSAGE_FROM_SYSTEM
0x00001000
Функция должна искать в системном ресурсе (ах) таблицы сообщений затребованное сообщение. Если этот флажок установлен вместе с
FORMAT_MESSAGE_FROM_HMODULE, функция ищет в
системной таблице сообщений, если сообщение не найдено в модуле, указанном параметром
lpSource. Этот флажок не может использоваться совместно с
FORMAT_MESSAGE_FROM_STRING.

Если этот флажок установлен, приложение может передать результат работы функции
GetLastError, чтобы извлечь текст сообщения для определенной системой ошибки.

FORMAT_MESSAGE_IGNORE_INSERTS
0x00000200
Последовательности вставки в определении сообщения должны игнорироваться и передаваться неизменными в буфер выводимых данных. Этот флажок полезен для выборки сообщения для более позднего форматирования. Если это флажок установлен, параметр
Arguments игнорируется.

Младший байт dwFlags может определить максимальную длину выводимой форматированной строки. Ниже перечисляются возможные значения младшего байта.

Значение

Предназначение

0 Нет никаких ограничений длины строки вывода данных. Функция хранит разрывы строки, которые находятся в тексте определения сообщения в буфере выводимых данных.
Не нулевое, а другое, чем
FORMAT_MESSAGE_MAX_WIDTH_MASK
Ненулевое значение — максимальное число
символов в строке вывода. Функция игнорирует обычные разрывы строки в
тексте определения сообщения. Функция никогда не рвет строку,
разграниченную незаполненным пространством по всему
разрыву строки. Функция хранит жестко
закодированные разрывы строки в тексте определения сообщения в буфере
выводимых данных. Жестко закодированные разрывы строки закодированы
при помощи %n ESC-последовательностей.
FORMAT_MESSAGE_MAX_WIDTH_MASK
0x000000FF
Функция игнорирует стандартные разрывы строки в тексте определения сообщения. Функция хранит жестко закодированные разрывы строки в тексте определения сообщения в буфер выводимых
данных. Функция не создает новых разрывов строки.

lpSource

Расположение определения сообщения. Тип этого параметра зависит от параметров настройки в параметре
dwFlags.

Установка dwFlags

Предназначение

FORMAT_MESSAGE_FROM_HMODULE
0x00000800
Дескриптор модуля, который содержит таблицу поиска сообщения.
FORMAT_MESSAGE_FROM_STRING
0x00000400
Указатель на строку, которая состоит из неформатированного текста сообщения. Она должна быть развернута для вставок и форматирования соответственно.

Если ни один из этих флажков не устанавлен в dwFlags, то параметр
lpSource игнорируется.

dwMessageId

Идентификатор сообщения для затребованного сообщения. Этот параметр игнорируется, если
dwFlags включает в себя FORMAT_MESSAGE_FROM_STRING.

dwLanguageId

Идентификатор языка для затребованного сообщения. Этот параметр игнорируется, если
dwFlags включает в себя FORMAT_MESSAGE_FROM_STRING.

Если Вы передаёте конкретный LANGID в этом параметре, то FormatMessage возвратит сообщение только для этого
LANGID. Если функция не может найти сообщение для этого LANGID, она возвращает  значение
ERROR_RESOURCE_LANG_NOT_FOUND. Если Вы передаете нуль,
FormatMessage ищет сообщение для LANGIDs в нижеследующем порядке:

  1. Неопределенный язык
  2. Потоковый LANGID, основывается на потоках с локализированными значениями
  3. Пользовательский по умолчанию LANGID, основывается на локализированном значении языка страны (местности) пользователя по умолчанию
  4. Системный по умолчанию LANGID, основывается на системном значении локализации по умолчанию
  5. Английский язык в США

Если FormatMessage не локализирует сообщения для какого-либо предшествующего
LANGIDs, то она возвращает любую языковую строку сообщения, которая присутствует. Если функция завершается ошибкой, то возвращает значение
ERROR_RESOURCE_LANG_NOT_FOUND.

lpBuffer

Указатель на буфер, который получает строку с завершающим нулем, устанавливающую форматированное сообщение. Если параметр
dwFlags включает в себя значение FORMAT_MESSAGE_ALLOCATE_BUFFER, функция назначает буфер, используя функцию
LocalAlloc и помещает указатель на буфер по адресу, указанному в параметре
lpBuffer.

Этот буфер не может быть больше, чем 64 КБ.

nSize

Если флажок FORMAT_MESSAGE_ALLOCATE_BUFFER не установлен, то этот параметр определяет размер буфера выводимых данных, в
TCHARs. Если FORMAT_MESSAGE_ALLOCATE_BUFFER установлен, то этот параметр определяет минимальное число
TCHARs, назначаемое для буфера выводимых данных.

Этот буфер не может быть больше, чем 64 КБ.

Arguments

Массив значений, которые используются как значения вставки в форматированном сообщении. %1 в форматирующей строке указывает первое значение в массиве
Arguments; %2 указывает второй параметр; и так далее.

Интерпретация каждого значения зависит от информации форматирования, связанной со вставкой в определение сообщения. Значение по умолчанию должно обработать каждое значение как указатель на строку с завершающим нулем.

По умолчанию, параметр Arguments имеет тип va_list *, который является конкретным для языка и конкретным для реализации типом данных для того, чтобы характеризовать переменное число параметров. Состояние параметра
va_list не
определяется по возвращению из функции. Чтобы использовать va_list снова, ликвидируйте указатель  списка переменных параметров, используя
va_begin и повторно инициализируйте его при помощи va_start.

Если у Вас нет указателя типа va_list *, то установите флажок
FORMAT_MESSAGE_ARGUMENT_ARRAY и передайте указатель на массив значений
DWORD_PTR; эти значения — вводимые данные в сообщение, форматированное как значения вставки. У каждой вставки должен быть соответствующий элемент в массиве.

Windows Me/98/95:  Никакая отдельная строка включения не может выйти за пределы 1023 символов по длине.

Возвращаемое значение

Если функция завершается успешно, возвращаемое значение — число TCHARs, сохраненное в буфере выводимых данных, исключая символ завершающего нуля.

Если функция завершается ошибкой, возвращаемое значение — нуль. Чтобы получить дополнительную информацию об ошибке, вызовите
GetLastError.

Замечания

Внутри текста сообщения, несколько ESC-последовательностей поддерживаются для того, чтобы динамически форматировать сообщение. Эти ESC-последовательности и их значения показаны в нижеследующих таблицах. Все ESC-последовательности
начинаются с символа процента (%).

ESC-последовательности

Предназначение

%0

Завершает строку текста сообщения без замыкающего знака новой строки. Эта ESC-последовательность может быть использована для создания длинных строк или для завершения сообщения непосредственно без
замыкающего знака новой строки. Это полезно для сообщений приглашения для ввода в командной строке.

%n!format string!

Идентифицирует вставку. Значение n может быть в диапазоне от 1 до 99. Форматирующая строка, которая должна быть окружена восклицательными знаками, является дополнительной (необязательной) и значениями по умолчанию
!s!, если не определены. Дополнительную информацию смотри в статье
Поля спецификации формата.

Форматирующая строка может включить в себя спецификаторы ширины и точности для строк и спецификатор ширины для целых чисел. Используйте звездочку (*), чтобы установить ширину и точность. Например, %1! *.*s!
или %1! *u!.

Если Вы не используете спецификаторы ширины и точности, числа вставки соответствуют непосредственно входным параметрам. Например, если исходная строка «%1 %2 %1», а вводимые параметры — «Билл» и «Боб», то
форматированная строка вывода — «Билл Боб Билл».

Однако, если Вы используете спецификатор ширины и точности, числа вставки не соответствуют непосредственно вводимым параметрам. Например, числа вставки для предыдущего примера могут поменяться на «%1! *.*s! %4
%5! *s!».

Числа вставки зависят от того, используете ли Вы массив параметров (FORMAT_MESSAGE_ARGUMENT_ARRAY) или
va_list. Для массива параметров следующее число вставки — n+2, если
предыдущая форматирующая строка содержала в себе одну звездочку и является n+3, если определялись две звездочки. Для
va_list следующее число вставки — n+1, если предыдущая форматирующая строка содержала одну
звездочку и является n+2, если определялись две звездочки.

Если Вы хотите повторить «Билл», как в предыдущем примере, параметры должны включить в себя «Билл» дважды. Например, если исходная строка «%1! *.*s! %4 %5! *s!»,
то параметры могут быть, 4, 2, Билл, Боб, 6, Билл
(если используется флажок FORMAT_MESSAGE_ARGUMENT_ARRAY). Форматированная строка тогда будет  » Би Боб   Билл».

Повторение чисел вставки, когда исходная строка содержит в себе спецификаторы ширины и точности, возможно, не приводит к предполагаемым результатам. Если бы Вы заменили %5 на %1, то функция сделала бы попытку
отобразить строку по адресу 6 (вероятно заканчивающейся нарушением прав доступа).

Спецификаторы формата с плавающей точкойe, E, f и g — не поддерживаются. Для обходного пути нужно использовать функцию
StringCchPrintf, чтобы форматировать число с плавающей запятой
во временном буфере, а затем использовать этот буфер как строку вставки.

Вставки, которые используют префикс I64, обрабатываются как два 32-разрядных параметра. Они должны использоваться перед тем, как используются, последующие параметры.

 


Обратите внимание!
на то, что может быть легче для Вас использовать
StringCchPrintf вместо этого префикса.

Любой другой символ нецифры после символа процента форматируется в выводимом сообщении без символа процента. Ниже — некоторые примеры.

Форматирующая строка

Окончательный вывод

%% Единичный знак процента.
%space Единичный пробел. Эта форматирующая строка может быть использована, чтобы гарантировать соответствующее число пробелов в строке текста сообщения.
%. Единичная точка. Эта форматирующая строка может быть использована для включения единичной точки в начале строки, не завершая определение текста сообщения.
%! Единичный восклицательный знак. Эта форматирующая строка может быть использована, чтобы включить восклицательный знак непосредственно после вставки без того, чтобы он был принят началом форматирующей строки.
%n Жесткий разрыв строки, когда форматирующая строка действует в конце строки. Эта форматирующая строка полезна, когда функция
FormatMessage поставляет обычные разрывы строки, таким образом сообщение помещается в
определенном размере.
%r Жесткий возврат каретки без замыкающего знака перевода на новую строку.
%t Единичная позиция табуляции.

Замечания по безопасности


Если эта функция вызванная без флажка FORMAT_MESSAGE_IGNORE_INSERTS, то параметр
Arguments должен содержать в себе достаточно многие параметры, чтобы удовлетворить все последовательности вставки в строке
сообщения, и они должны иметь корректный тип. По этой причине, не используйте ненадежные или неизвестные строки сообщений с включенными вставками, потому что они могут содержать в себе больше
последовательностей включения, чем, параметр Arguments обеспечивает, или они могут иметь ошибочный тип. В частности опасно получать случайные коды системных ошибок, возвращенные из
API и использования флажка FORMAT_MESSAGE_FROM_SYSTEM без флажка
FORMAT_MESSAGE_IGNORE_INSERTS.

Демонстрационный код [C++]

Функция FormatMessage может быть использована, чтобы получать строки сообщения об ошибках для кодов системных ошибок, возвращенных функцией
GetLastError. Пример смотри в статье Получение последнего кода ошибки.
Нижеследующие примеры также демонстрируют использования функции FormatMessage.

Пример ниже показывает, как использовать массив параметра и спецификаторы ширины и точности.

#ifndef UNICODE
#define UNICODE
#endif

#include <windows.h>
#include <stdio.h>

void main(void)
{
    LPWSTR pMessage = L"%1!*.*s! %4 %5!*s!";
    DWORD_PTR pArgs[] = { (DWORD_PTR)4, (DWORD_PTR)2, (DWORD_PTR)L"Bill",  // %1!*.*s!
         (DWORD_PTR)L"Bob",                                                // %4
         (DWORD_PTR)6, (DWORD_PTR)L"Bill" };                               // %5!*s!
    const DWORD size = 100+1;
    WCHAR buffer[size];


    if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                       pMessage, 
                       0,  // игнорируется
                       0,  // игнорируется
                       (LPWSTR)&buffer, 
                       size, 
                       (va_list*)pArgs))
    {
        wprintf(L"Format message failed with 0x%xn", GetLastError());
        return;
    }

    wprintf(L"Formatted message: %sn", buffer);
}

Демонстрационный код

Пример ниже показывает, как реализовать осуществить предыдущий пример, используя
va_list.

#ifndef UNICODE
#define UNICODE
#endif

#include <windows.h>
#include <stdio.h>

LPWSTR GetFormattedMessage(LPWSTR pMessage, ...);

void main(void)
{
    LPWSTR pBuffer = NULL;
    LPWSTR pMessage = L"%1!*.*s! %3 %4!*s!";

    pBuffer = GetFormattedMessage(pMessage, 4, 2, L"Bill", L"Bob", 6, L"Bill");
    if (pBuffer)
    {
        wprintf(L"Formatted message: %sn", pBuffer);
        LocalFree(pBuffer);
    }
    else
    {
        wprintf(L"Format message failed with 0x%xn", GetLastError());
    }
}

LPWSTR GetFormattedMessage(LPWSTR pMessage, ...)
{
    LPWSTR pBuffer = NULL;

    va_list args = NULL;
    va_start(args, pMessage);

    FormatMessage(FORMAT_MESSAGE_FROM_STRING |
                  FORMAT_MESSAGE_ALLOCATE_BUFFER,
                  pMessage, 
                  0,  // игнорируется
                  0,  // игнорируется
                  (LPWSTR)&pBuffer, 
                  0, 
                  &args);

    va_end(args);

    return pBuffer;
}

Смотри также

Обзор Обработка ошибок, Функции, используемые при обработке ошибок, Ресурс MESSAGETABLE, Компилятор сообщения, Таблицы сообщений

Размещение и совместимость FormatMessage

К Windows Vista Да
л Windows XP Да
и Windows 2000 Professional Да
е Windows NT Workstation Да версии 3.1
н Windows Me Да
т Windows 98 Да
  Windows 95 Да
С Windows Server 2008  
е Windows Server 2003 Да
р Windows 2000 Server Да
в Windows NT Server Да
версии 3.1
е
р
Используемая библиотека Kernel32.lib
Используемая DLL Kernel32.dll
Заголовочный файл
— объявлено в Winbase.h
— включено в Windows.h
Unicode Реализуется как FormatMessageW (Unicode)
и
FormatMessageA (ANSI)
Замечания по платформе Не имеется

Hosted by uCoz

Example

GetLastError returns a numerical error code. To obtain a descriptive error message (e.g., to display to a user), you can call FormatMessage:

// This functions fills a caller-defined character buffer (pBuffer)
// of max length (cchBufferLength) with the human-readable error message
// for a Win32 error code (dwErrorCode).
// 
// Returns TRUE if successful, or FALSE otherwise.
// If successful, pBuffer is guaranteed to be NUL-terminated.
// On failure, the contents of pBuffer are undefined.
BOOL GetErrorMessage(DWORD dwErrorCode, LPTSTR pBuffer, DWORD cchBufferLength)
{
    if (cchBufferLength == 0)
    {
        return FALSE;
    }

    DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                 NULL,  /* (not used with FORMAT_MESSAGE_FROM_SYSTEM) */
                                 dwErrorCode,
                                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                 pBuffer,
                                 cchBufferLength,
                                 NULL);
    return (cchMsg > 0);
}

In C++, you can simplify the interface considerably by using the std::string class:

#include <Windows.h>
#include <exception>
#include <stdexcept>
#include <memory>
#include <string>
typedef std::basic_string<TCHAR> String;

String GetErrorMessage(DWORD dwErrorCode)
{
    LPTSTR psz = NULL;
    const DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
                                         | FORMAT_MESSAGE_IGNORE_INSERTS
                                         | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                                       NULL, // (not used with FORMAT_MESSAGE_FROM_SYSTEM)
                                       dwErrorCode,
                                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                       reinterpret_cast<LPTSTR>(&psz),
                                       0,
                                       NULL);
    if (cchMsg > 0)
    {
        // Assign buffer to smart pointer with custom deleter so that memory gets released
        // in case String's c'tor throws an exception.
        auto deleter = [](void* p) { ::HeapFree(::GetProcessHeap(), 0, p); };
        std::unique_ptr<TCHAR, decltype(deleter)> ptrBuffer(psz, deleter);
        return String(ptrBuffer.get(), cchMsg);
    }
    else
    {
        throw std::runtime_error("Failed to retrieve error message string.");
    }
}

NOTE: These functions also work for HRESULT values. Just change the first parameter from DWORD dwErrorCode to HRESULT hResult. The rest of the code can remain unchanged.

Понравилась статья? Поделить с друзьями:
  • Winapi error codes
  • Winamp как изменить скорость воспроизведения
  • Winamp как изменить размер окна
  • Winamp error reporter
  • Winafl error opening pidfile txt