Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Com używa wartości HRESULT, aby wskazać powodzenie lub niepowodzenie metody lub wywołania funkcji. Różne nagłówki zestawu SDK definiują stałe HRESULT. Typowy zestaw kodów dla całego systemu jest zdefiniowany w pliku WinError.h. W poniższej tabeli przedstawiono niektóre z tych kodów powrotnych dla całego systemu.
| Stały | Wartość liczbowa | Opis |
|---|---|---|
| E_ACCESSDENIED | 0x80070005 | Odmowa dostępu. |
| E_FAIL | 0x80004005 | Nieokreślony błąd. |
| E_INVALIDARG | 0x80070057 | Nieprawidłowa wartość parametru. |
| E_OUTOFMEMORY | 0x8007000E | Brak pamięci. |
| E_POINTER | 0x80004003 | NULL został niepoprawnie przekazany jako wartość wskaźnika. |
| E_UNEXPECTED | 0x8000FFFF | Nieoczekiwany warunek. |
| S_OK | 0x0 | Sukces. |
| S_FALSE | 0x1 | Sukces. |
Wszystkie stałe z prefiksem "E_" to kody błędów. Stałe S_OK i S_FALSE są kodami powodzenia. Prawdopodobnie 99% metod COM zwraca S_OK po pomyślnym zakończeniu; ale nie pozwól, aby ten fakt wprowadził cię w błąd. Metoda może zwracać inne kody sukcesu, dlatego zawsze testuj pod kątem błędów, używając makra SUCCEEDED lub FAILED. Poniższy przykładowy kod przedstawia niewłaściwy sposób i właściwy sposób testowania powodzenia wywołania funkcji.
// Wrong.
HRESULT hr = SomeFunction();
if (hr != S_OK)
{
printf("Error!\n"); // Bad. hr might be another success code.
}
// Right.
HRESULT hr = SomeFunction();
if (FAILED(hr))
{
printf("Error!\n");
}
Kod sukcesu S_FALSE zasługuje na wzmiankę. Niektóre metody używają S_FALSE, aby oznaczać w sensie negatywną sytuację, która nie jest awarią. Może również wskazywać wartość "no-op"— metoda powiodła się, ale nie miała żadnego efektu. Na przykład funkcja CoInitializeEx zwraca S_FALSE, jeśli wywołasz ją po raz drugi z tego samego wątku. Jeśli musisz odróżnić S_OK i S_FALSE w kodzie, powinieneś przetestować wartość bezpośrednio, ale nadal użyj FAILED lub SUCCEEDED do obsługi pozostałych przypadków, jak pokazano w poniższym przykładowym kodzie.
if (hr == S_FALSE)
{
// Handle special case.
}
else if (SUCCEEDED(hr))
{
// Handle general success case.
}
else
{
// Handle errors.
printf("Error!\n");
}
Niektóre wartości HRESULT są specyficzne dla określonej funkcji lub podsystemu systemu Windows. Na przykład interfejs API grafiki Direct2D definiuje kod błędu D2DERR_UNSUPPORTED_PIXEL_FORMAT, co oznacza, że program używał nieobsługiwanego formatu pikseli. Dokumentacja systemu Windows często zawiera listę określonych kodów błędów, które może zwrócić metoda. Nie należy jednak traktować tych list jako ostatecznych. Metoda może zawsze zwracać wartość HRESULT, która nie jest wymieniona w dokumentacji. Ponownie użyj makr SUCCEEDED i FAILED. Jeśli testujesz określony kod błędu, dołącz również przypadek domyślny.
if (hr == D2DERR_UNSUPPORTED_PIXEL_FORMAT)
{
// Handle the specific case of an unsupported pixel format.
}
else if (FAILED(hr))
{
// Handle other errors.
}
Wzorce obsługi błędów
W tej sekcji przedstawiono pewne wzorce obsługi błędów COM w sposób ustrukturyzowany. Każdy wzorzec ma zalety i wady. Do pewnego stopnia wybór jest kwestią smaku. Jeśli pracujesz nad istniejącym projektem, może to już mieć wytyczne dotyczące kodowania, które przypisują określony styl. Niezależnie od tego, który wzorzec jest wdrażany, niezawodny kod będzie przestrzegać następujących reguł.
- Dla każdej metody lub funkcji zwracającej HRESULTsprawdź wartość zwracaną przed kontynuowaniem.
- Zwalnianie zasobów po ich użyciu.
- Nie należy podejmować próby uzyskania dostępu do nieprawidłowych lub niezainicjowanych zasobów, takich jak wskaźniki NULL.
- Nie próbuj używać zasobu po jego wydaniu.
Mając na uwadze te reguły, poniżej przedstawiono cztery wzorce obsługi błędów.
Zagnieżdżone ifs
Po każdym wywołaniu, które zwraca HRESULT, użyj instrukcji , jeśli, aby sprawdzić powodzenie. Następnie umieść następne wywołanie metody w obrębie zakresu w instrukcji if. Więcej , jeśli instrukcje można zagnieżdżać tak głęboko, jak to konieczne. Poprzednie przykłady kodu w tym module używały tego wzorca, ale oto ponownie:
HRESULT ShowDialog()
{
IFileOpenDialog *pFileOpen;
HRESULT hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileOpen));
if (SUCCEEDED(hr))
{
hr = pFileOpen->Show(NULL);
if (SUCCEEDED(hr))
{
IShellItem *pItem;
hr = pFileOpen->GetResult(&pItem);
if (SUCCEEDED(hr))
{
// Use pItem (not shown).
pItem->Release();
}
}
pFileOpen->Release();
}
return hr;
}
Zalety
- Zmienne można zadeklarować z minimalnym zakresem. Na przykład pItem nie jest zadeklarowany, dopóki nie zostanie użyty.
- W każdej instrukcji i pewne niezmienniki są prawdziwe: wszystkie poprzednie wywołania zakończyły się pomyślnie, a wszystkie pozyskane zasoby są nadal ważne. W poprzednim przykładzie, gdy program osiągnie najbardziej wewnętrzną instrukcję 'if', zarówno pItem, jak i pFileOpen są znane jako prawidłowe.
- Jest jasne, kiedy należy zwolnić wskaźniki interfejsu i inne zasoby. Zwalniasz zasób na końcu , jeśli instrukcja natychmiast następuje po wywołaniu, które nabyło zasób.
Wady
- Niektórzy ludzie uważają głębokie zagnieżdżanie trudne do zrozumienia.
- Obsługa błędów jest mieszana z innymi instrukcjami rozgałęziania i pętli. Może to utrudnić ogólną logikę programu.
Warunki kaskadowe
Po każdym wywołaniu metody użyj instrukcji , jeśli, aby sprawdzić powodzenie. Jeśli metoda powiedzie się, umieść następne wywołanie metody wewnątrz bloku , jeśli. Jednak zamiast zagnieżdżać dalsze , jeśli instrukcje, umieść każdy kolejny test SUCCEEDED po poprzednim , jeśli bloku. Jeśli jakakolwiek metoda zakończy się niepowodzeniem, wszystkie pozostałe testy SUCCEEDED po prostu kończą się niepowodzeniem do momentu osiągnięcia dolnej części funkcji.
HRESULT ShowDialog()
{
IFileOpenDialog *pFileOpen = NULL;
IShellItem *pItem = NULL;
HRESULT hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileOpen));
if (SUCCEEDED(hr))
{
hr = pFileOpen->Show(NULL);
}
if (SUCCEEDED(hr))
{
hr = pFileOpen->GetResult(&pItem);
}
if (SUCCEEDED(hr))
{
// Use pItem (not shown).
}
// Clean up.
SafeRelease(&pItem);
SafeRelease(&pFileOpen);
return hr;
}
W tym wzorcu zwalniasz zasoby na samym końcu funkcji. Jeśli wystąpi błąd, niektóre wskaźniki mogą być nieprawidłowe, gdy funkcja zakończy działanie. Wywołanie Release na nieprawidłowym wskaźniku spowoduje awarię programu (lub nawet gorsze skutki), więc należy zainicjować wszystkie wskaźniki jako NULL i sprawdzić, czy są NULL, zanim zostaną zwolnione. W tym przykładzie użyto funkcji SafeRelease; inteligentne wskaźniki są również dobrym wyborem.
Jeśli używasz tego wzorca, należy zachować ostrożność przy użyciu konstrukcji pętli. Wewnątrz pętli przerwij pętlę, jeśli jakiekolwiek wywołanie zakończy się niepowodzeniem.
Zalety
- Ten wzorzec tworzy mniej zagnieżdżania niż wzorzec "zagnieżdżonych ifs".
- Ogólny przepływ sterowania jest łatwiejszy do zobaczenia.
- Zasoby są zwalniane w określonym miejscu w kodzie.
Niekorzyści
- Wszystkie zmienne muszą być zadeklarowane i zainicjowane w górnej części funkcji.
- Jeśli wywołanie zakończy się niepowodzeniem, funkcja wykonuje wiele niepotrzebnych testów błędów, zamiast natychmiast zamykać funkcję.
- Ponieważ przepływ sterowania jest kontynuowany przez funkcję po awarii, należy zachować ostrożność w całej treści funkcji, aby nie uzyskiwać dostępu do nieprawidłowych zasobów.
- Błędy wewnątrz pętli wymagają specjalnego traktowania.
Reakcja na awarię
Po każdym wywołaniu metody sprawdź, czy nie wystąpiło niepowodzenie. W przypadku niepowodzenia przejdź do etykiety znajdującej się blisko dolnej części funkcji. Po wykonaniu operacji związanej z etykietą, ale przed wyjściem z funkcji, należy zwolnić zasoby.
HRESULT ShowDialog()
{
IFileOpenDialog *pFileOpen = NULL;
IShellItem *pItem = NULL;
HRESULT hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileOpen));
if (FAILED(hr))
{
goto done;
}
hr = pFileOpen->Show(NULL);
if (FAILED(hr))
{
goto done;
}
hr = pFileOpen->GetResult(&pItem);
if (FAILED(hr))
{
goto done;
}
// Use pItem (not shown).
done:
// Clean up.
SafeRelease(&pItem);
SafeRelease(&pFileOpen);
return hr;
}
Zalety
- Ogólny przepływ sterowania jest łatwy do zobaczenia.
- W każdym momencie kodu po sprawdzeniu niepowodzenia , jeśli nie przejdziesz do etykiety, gwarantowane jest, że wszystkie poprzednie wywołania zakończyły się pomyślnie.
- Zasoby są zwalniane w jednym miejscu w kodzie.
Wady
- Wszystkie zmienne muszą być zadeklarowane i zainicjowane w górnej części funkcji.
- Niektórzy programiści nie lubią używać goto w kodzie. (Należy jednak zauważyć, że użycie goto jest wysoce ustrukturyzowane; kod nigdy nie przechodzi poza bieżące wywołanie funkcji.
- instrukcje goto pomijają inicjalizatory.
Rzuć na błąd
Zamiast przechodzić do etykiety, możesz zgłosić wyjątek w przypadku niepowodzenia metody. Może to spowodować bardziej idiomatyczny styl języka C++, jeśli przyzwyczaiłeś się do pisania kodu bezpiecznego dla wyjątków.
#include <comdef.h> // Declares _com_error
inline void throw_if_fail(HRESULT hr)
{
if (FAILED(hr))
{
throw _com_error(hr);
}
}
void ShowDialog()
{
try
{
CComPtr<IFileOpenDialog> pFileOpen;
throw_if_fail(CoCreateInstance(__uuidof(FileOpenDialog), NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileOpen)));
throw_if_fail(pFileOpen->Show(NULL));
CComPtr<IShellItem> pItem;
throw_if_fail(pFileOpen->GetResult(&pItem));
// Use pItem (not shown).
}
catch (_com_error err)
{
// Handle error.
}
}
Zwróć uwagę, że w tym przykładzie użyto klasy CComPtr do zarządzania wskaźnikami interfejsu. Ogólnie rzecz biorąc, jeśli kod zgłasza wyjątki, należy postępować zgodnie ze wzorcem RAII (pozyskiwanie zasobów jest inicjowaniem). Oznacza to, że każdy zasób powinien być zarządzany przez obiekt, którego destruktor gwarantuje, że zasób jest poprawnie zwolniony. Jeśli zgłaszany jest wyjątek, destruktor zostanie na pewno wywołany. W przeciwnym razie program może wyciekać zasobów.
Zalety
- Zgodny z istniejącym kodem korzystającym z obsługi wyjątków.
- Zgodność z bibliotekami języka C++, które zgłaszają wyjątki, takie jak biblioteka szablonów standardowych (STL).
Wady
- Wymaga obiektów języka C++ do zarządzania zasobami, takimi jak dojścia pamięci lub plików.
- Wymaga dobrego zrozumienia sposobu pisania kodu bezpiecznego dla wyjątków.
Dalej
Moduł 3. Grafika systemu Windows