Win32 API
Błąd raportowania i obsługi
Szukaj…
Uwagi
Każdy wątek będzie miał swój ostatni kod błędu. Interfejs API systemu Windows ustawi ostatni kod błędu w wątku wywołującym.
Należy zawsze wywoływać funkcję GetLastError
natychmiast po sprawdzeniu wartości zwracanej przez funkcję Windows API.
Większość funkcji Windows API ustawia ostatni kod błędu, gdy zawodzą. Niektórzy ustawią również ostatni kod błędu, gdy się powiedzie. Istnieje wiele funkcji, które nie ustawiają ostatniego kodu błędu. Zawsze zapoznaj się z dokumentacją funkcji Windows API.
Używanie FORMAT_MESSAGE_FROM_SYSTEM
bez FORMAT_MESSAGE_IGNORE_INSERTS
podczas korzystania z funkcji FormatMessage
celu uzyskania opisu kodu błędu jest niebezpieczne.
Wprowadzenie
Interfejs API systemu Windows jest dostarczany za pomocą interfejsu C-callable. Powodzenie lub niepowodzenie wywołania interfejsu API jest zgłaszane wyłącznie za pomocą zwracanych wartości. Wyjątki nie są częścią udokumentowanej umowy (chociaż niektóre implementacje API mogą zgłaszać wyjątki SEH , np. Przy przekazywaniu argumentu lpCommandLine tylko do odczytu do CreateProcess ).
Zgłaszanie błędów z grubsza należy do jednej z czterech kategorii:
- Zwraca tylko wartość
- Zwraca wartość z dodatkowymi informacjami o awarii
- Zwraca wartość z dodatkowymi informacjami o niepowodzeniu i sukcesie
-
HRESULT
wartośćHRESULT
Dokumentacja każdego wywołania interfejsu API wyraźnie wzywa, w jaki sposób zgłaszane są błędy. Zawsze zapoznaj się z dokumentacją.
Błąd zgłaszany tylko przez wartość zwracaną
Niektóre wywołania API zwracają pojedynczą flagę niepowodzenia / sukcesu, bez żadnych dodatkowych informacji (np. GetObject ):
if ( GetObjectW( obj, 0, NULL ) == 0 ) {
// Failure: no additional information available.
}
Zgłoszony błąd z dodatkowymi informacjami o awarii
Oprócz wartości zwracanej w przypadku niepowodzenia / powodzenia, niektóre wywołania API również ustawiają ostatni błąd w przypadku awarii (np. CreateWindow ). Dokumentacja zwykle zawiera następujące standardowe sformułowanie dla tego przypadku:
Jeśli funkcja się powiedzie, zwracana wartość to <wartość sukcesu specyficzna dla API> .
Jeśli funkcja zawiedzie, zwracana wartość to <wartość błędu specyficzna dla API> . Aby uzyskać rozszerzone informacje o błędzie, wywołaj GetLastError .
if ( CreateWindowW( ... ) == NULL ) {
// Failure: get additional information.
DWORD dwError = GetLastError();
} else {
// Success: must not call GetLastError.
}
Ważne jest, aby wywołać NATYCHMIAST GetLastError()
. Ostatni kod błędu może zostać zastąpiony przez dowolną inną funkcję, więc jeśli istnieje dodatkowe wywołanie funkcji między funkcją, która zakończyła się niepowodzeniem, a wywołaniem GetLastError()
, powrót z GetLastError()
nie będzie już wiarygodny. Zachowaj szczególną ostrożność podczas pracy z konstruktorami C ++.
Gdy otrzymasz kod błędu, musisz go zinterpretować. Pełną listę kodów błędów można uzyskać w witrynie MSDN na stronie System Error Codes (Windows) . Alternatywnie możesz zajrzeć do plików nagłówkowych systemu; plik ze wszystkimi stałymi kodami błędów to winerror.h
. (Jeśli masz oficjalny zestaw SDK firmy Microsoft dla systemu Windows 8 lub nowszego, znajduje się on w shared
podfolderze folderu dołączanego).
Uwagi na temat wywoływania funkcji GetLastError()
w innych językach programowania
Języki .net (C #, VB itp.)
Dzięki .net nie powinieneś P / Invoke bezpośrednio w GetLastError()
. Wynika to z faktu, że środowisko wykonawcze .net wykona inne wywołania API systemu Windows w tym samym wątku za twoimi plecami. Na przykład śmieciarz może wywołać VirtualFree()
jeśli znajdzie wystarczającą ilość pamięci, której już nie używa, i może się to zdarzyć między zamierzonym wywołaniem funkcji a wywołaniem GetLastError()
.
Zamiast tego .net zapewnia funkcję Marshal.GetLastWin32Error()
, która pobierze ostatni błąd z ostatniego wywołania P / Invoke, które sam Marshal.GetLastWin32Error()
. Użyj tego zamiast wywoływać bezpośrednio GetLastError()
.
(.net wydaje się nie przeszkadzać w importowaniu GetLastError()
; nie jestem pewien, dlaczego).
Udać się
Różne funkcje Go do wywoływania funkcji DLL (które znajdują się zarówno w pakiecie syscall
i pakiecie golang.org/x/sys/windows
) zwracają trzy wartości: r1
, r2
i err
. r2
nigdy nie jest używane; możesz tam użyć pustego identyfikatora. r1
jest wartością zwracaną przez funkcję. err
jest wynikiem wywołania GetLastError()
ale przekształcony w typ, który implementuje error
, dzięki czemu można przekazać go do funkcji wywołujących do obsługi.
Ponieważ Go nie wie, kiedy wywołać GetLastError()
a kiedy nie, zawsze zwróci błąd inny niż nil
. Dlatego typowy id obsługi błędów Go
r1, _, err := syscall.Syscall12(CreateWindowW.Addr(), ...)
if err != nil {
// handle err
}
// use r1
nie będzie działać. Zamiast tego musisz sprawdzić r1
, dokładnie tak, jak w C, i użyć err
, tylko jeśli oznacza to , że funkcja zwróciła błąd:
r1, _, err := syscall.Syscall12(CreateWindowW.Addr(), ...)
if r1 == 0 {
// handle err
}
// use r1
Zgłoszony błąd z dodatkowymi informacjami o niepowodzeniu i sukcesie
Niektóre wywołania interfejsu API mogą zakończyć się powodzeniem lub niepowodzeniem na więcej niż jeden sposób. Interfejsy API zwykle zwracają dodatkowe informacje zarówno dla udanych wywołań, jak i błędów (np. 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.
}
}
Błąd zgłoszony jako wartość HRESULT
HRESULT to 32-bitowe wartości liczbowe, w których bity lub zakresy bitów kodują dobrze zdefiniowane informacje. MSB jest flagą niepowodzenia / sukcesu, a pozostałe bity przechowują dodatkowe informacje. Niepowodzenie lub sukces można określić za pomocą makr FAILED lub SUCCEEDED . HRESULT
są powszechnie używane z COM, ale pojawiają się również w implementacjach innych niż COM (np. 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).
...
}
}
Konwertowanie kodu błędu na ciąg komunikatu
GetLastError
zwraca numeryczny kod błędu. Aby uzyskać opisowy komunikat o błędzie ( np. Wyświetlany użytkownikowi), możesz wywołać 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);
}
W C ++ można znacznie uprościć interfejs, używając klasy 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.");
}
}
UWAGA: Te funkcje działają również dla wartości HRESULT
. Wystarczy zmienić pierwszy parametr z DWORD dwErrorCode
na HRESULT hResult
. Reszta kodu może pozostać niezmieniona.