Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
COM använder HRESULT- värden för att indikera att en metod eller ett funktionsanrop lyckades eller misslyckades. Olika SDK-huvuden definierar olika HRESULT- konstanter. En gemensam uppsättning systemomfattande koder definieras i WinError.h. I följande tabell visas några av dessa systemomfattande returkoder.
| Konstant | Numeriskt värde | Beskrivning | 
|---|---|---|
| E_ACCESSDENIED | 0x80070005 | Åtkomst nekad. | 
| E_FAIL | 0x80004005 | Ospecificerat fel. | 
| E_INVALIDARG | 0x80070057 | Ogiltigt parametervärde. | 
| E_OUTOFMEMORY | 0x8007000E | Slut på minne. | 
| E_POINTER | 0x80004003 | NULL överfördes felaktigt för ett pekarvärde. | 
| E_UNEXPECTED | 0x8000FFFF | Oväntat tillstånd. | 
| S_OK | 0x0 | Framgång. | 
| S_FALSE | 0x1 | Framgång. | 
Alla konstanter med prefixet "E_" är felkoder. Konstanterna S_OK och S_FALSE är båda framgångskoder. Förmodligen returnerar 99% COM-metoder S_OK när de lyckas; men låt inte detta faktum vilseleda dig. En metod kan returnera andra lyckade koder, så kontrollera alltid för fel genom att använda SUCCEEDED eller FAILED makron. Följande exempelkod visar fel sätt och rätt sätt att testa för att ett funktionsanrop ska lyckas.
// 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"); 
}
Framgångskoden S_FALSE förtjänar att nämnas. Vissa metoder använder S_FALSE för att ungefär betyda ett negativt villkor som inte är ett fel. Det kan också tyda på en "no-op" – metoden lyckades, men hade ingen effekt. Funktionen CoInitializeEx returnerar till exempel S_FALSE om du anropar den en andra gång från samma tråd. Om du behöver skilja mellan S_OK och S_FALSE i koden bör du testa värdet direkt, men ändå använda FAILED eller SUCCEEDED för att hantera de återstående fallen, som du ser i följande exempelkod.
if (hr == S_FALSE)
{
    // Handle special case.
}
else if (SUCCEEDED(hr))
{
    // Handle general success case.
}
else
{
    // Handle errors.
    printf("Error!\n"); 
}
Vissa HRESULT- värden är specifika för en viss funktion eller ett visst undersystem i Windows. Till exempel definierar Direct2D-grafik-API:et felkoden D2DERR_UNSUPPORTED_PIXEL_FORMAT, vilket innebär att programmet använde ett pixelformat som inte stöds. Windows-dokumentationen innehåller ofta en lista över specifika felkoder som en metod kan returnera. Du bör dock inte betrakta dessa listor som slutgiltiga. En metod kan alltid returnera ett HRESULT- värde som inte finns med i dokumentationen. Använd återigen makrona SUCCEEDED och FAILED. Om du testar för en specifik felkod inkluderar du även ett standardfall.
if (hr == D2DERR_UNSUPPORTED_PIXEL_FORMAT)
{
    // Handle the specific case of an unsupported pixel format.
}
else if (FAILED(hr))
{
    // Handle other errors.
}
Mönster för felhantering
I det här avsnittet tittar vi på några mönster för att hantera COM-fel på ett strukturerat sätt. Varje mönster har fördelar och nackdelar. Till viss del är valet en smakfråga. Om du arbetar med ett befintligt projekt kanske det redan har kodningsriktlinjer som tillskriver ett visst format. Oavsett vilket mönster du använder följer robust kod följande regler.
- För varje metod eller funktion som returnerar en HRESULT-kontrollerar du returvärdet innan du fortsätter.
 - Frigör resurser när de har använts.
 - Försök inte komma åt ogiltiga eller oinitialiserade resurser, till exempel NULL pekare.
 - Försök inte använda en resurs när du har släppt den.
 
Med dessa regler i åtanke finns här fyra mönster för hantering av fel.
Kapslade ifs
Efter varje anrop som returnerar en HRESULT, använd en if-sats för att testa om det var framgångsrikt. Placera sedan nästa metodanrop inom omfånget för if-satsen. Mer om-instruktioner kan kapslas så djupt som behövs. De tidigare kodexemplen i den här modulen har alla använt det här mönstret, men här är det igen:
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;
}
Fördelar
- Variabler kan deklareras med minimalt omfång. Till exempel deklareras pItem- inte förrän den används.
 - Inom varje if-satsen är vissa invarianter sanna: Alla tidigare anrop har lyckats och alla förvärvade resurser är fortfarande giltiga. I det föregående exemplet, när programmet når den innersta om-instruktionen, är både pItem och pFileOpen kända som giltiga.
 - Det är tydligt när du ska släppa gränssnittspekare och andra resurser. Du släpper en resurs i slutet av i-instruktionen som omedelbart följer anropet som hämtade resursen.
 
Nackdelar
- Vissa människor tycker att djup kapsling är svårt att läsa.
 - Felhantering blandas med andra förgrenings- och loopningsinstruktioner. Detta kan göra den övergripande programlogik svårare att följa.
 
Kaskaderande if-satser
Efter varje metodanrop använder du en om-instruktion för att testa om det lyckas. Om metoden lyckas, placera nästa metodanrop i om block. Men i stället för att kapsla ytterligare om-instruktioner placerar du varje efterföljande SUCCEEDED-test efter föregående om block. Om en metod misslyckas, misslyckas helt enkelt alla återstående SUCCEEDED-tester tills botten av funktionen har nåtts.
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;
}
I det här mönstret släpper du resurser i slutet av funktionen. Om ett fel uppstår kan vissa pekare vara ogiltiga när funktionen avslutas. Om du anropar Release på en ogiltig pekare kraschar programmet (eller ännu värre), så måste du initiera alla pekare till NULL och kontrollera om de är NULL innan du släpper dem. I det här exemplet används funktionen SafeRelease; smarta pekare är också ett bra val.
Om du använder det här mönstret måste du vara försiktig med loopkonstruktioner. I en loop bryter du från loopen om något anrop misslyckas.
Fördelar
- Det här mönstret skapar färre kapslingar än mönstret "kapslade if-satser".
 - Det övergripande kontrollflödet är lättare att se.
 - Resurser släpps vid ett tillfälle i koden.
 
Nackdelar
- Alla variabler måste deklareras och initieras överst i funktionen.
 - Om ett anrop misslyckas gör funktionen flera onödiga felkontroller i stället för att avsluta funktionen omedelbart.
 - Eftersom kontrollflödet fortsätter genom funktionen efter ett programmeringsfel måste du vara försiktig i hela funktionens kropp så att du inte råkar använda ogiltiga resurser.
 - Fel i en loop kräver ett specialfall.
 
Hoppa vid fel
Efter varje metodanrop testar du för misslyckande (inte framgång). Vid fel hoppar du till en etikett nära slutet av funktionen. Efter etiketten, men innan du avslutar funktionen, frigör du resurser.
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;
}
Fördelar
- Det övergripande kontrollflödet är enkelt att se.
 - Vid varje punkt i koden efter en MISSLYCKADES kontroll, om du inte har hoppat till märkpunkten, är det garanterat att alla tidigare anrop har lyckats.
 - Resurser släpps på en plats i koden.
 
Nackdelar
- Alla variabler måste deklareras och initieras överst i funktionen.
 - Vissa programmerare gillar inte att använda goto i sin kod. (Det bör dock noteras att den här användningen av goto är mycket strukturerad. Koden hoppar aldrig utanför det aktuella funktionsanropet.)
 - goto-instruktioner hoppar över initierare.
 
Utlös vid fel
I stället för att gå till en etikett kan du utlösa ett undantag när en metod misslyckas. Detta kan ge en mer idiomatisk stil med C++ om du är van att skriva undantagssäker kod.
#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.
    }
}
Observera att det här exemplet använder klassen CComPtr för att hantera gränssnittspekare. Om koden utlöser undantag bör du vanligtvis följa mönstret RAII (Resource Acquisition is Initialization). Det innebär att varje resurs ska hanteras av ett objekt vars destructor garanterar att resursen släpps korrekt. Om ett undantag utlöses kommer destrutorn garanterat att anropas. Annars kan programmet läcka resurser.
Fördelar
- Kompatibel med befintlig kod som använder undantagshantering.
 - Kompatibel med C++-bibliotek som utlöser undantag, till exempel STL (Standard Template Library).
 
Nackdelar
- Kräver C++-objekt för att hantera resurser som minne eller filhandtag.
 - Kräver en god förståelse för hur du skriver undantagssäker kod.
 
Nästa