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