Megosztás a következőn keresztül:


Hibakezelés a COM-ban (a Win32 és a C++használatának első lépései)

A COM HRESULT értékekkel jelzi egy metódus vagy függvényhívás sikerességét vagy sikertelenségét. A különböző SDK-fejlécek különböző HRESULT állandókat határoznak meg. A rendszerszintű kódok közös készletét a WinError.h határozza meg. Az alábbi táblázat néhány ilyen rendszerszintű visszatérési kódot mutat be.

Konstans Számérték Leírás
Hozzáférés megtagadva 0x80070005 Hozzáférés megtagadva.
E_FAIL 0x80004005 Meghatározatlan hiba.
E_INVALIDARG 0x80070057 Érvénytelen paraméterérték.
E_OUTOFMEMORY 0x8007000E Nincs memóriája.
E_POINTER 0x80004003 NULL helytelenül lett átadva egy mutatóértékhez.
E_UNEXPECTED 0x8000FFFF Váratlan állapot.
S_OK 0x0 Sikeres.
S_FALSE 0x1 Siker.

 

A "E_" előtaggal rendelkező összes állandó hibakód. A S_OK és S_FALSE állandók egyaránt sikerkódok. A COM-metódusok 99% valószínűleg sikeres S_OK térnek vissza; de ne hagyja, hogy ez a tény félrevezeti. Egy metódus más sikerkódokat is visszaadhat, ezért mindig tesztelje a hibákat a SIKERES vagy SIKERTELEN makró használatával. Az alábbi példakód a függvényhívás sikerességét tesztelő helytelen és helyes módszert mutatja be.

// 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"); 
}

A sikerkód S_FALSE említést érdemel. Egyes módszerek S_FALSE körülbelül olyan negatív feltételt jelentenek, amely nem számít hibának. Azt is jelezheti, hogy "no-op" – a módszer sikeres volt, de nem volt hatása. A CoInitializeEx függvény például S_FALSE ad vissza, ha ugyanazt a szálat másodszor hívja meg. Ha meg kell különböztetnie a S_OK és a S_FALSE a kódban, közvetlenül tesztelje az értéket, de továbbra is használja a SIKERTELEN vagy SIKERES a fennmaradó esetek kezelésére, ahogyan az az alábbi példakódban látható.

if (hr == S_FALSE)
{
    // Handle special case.
}
else if (SUCCEEDED(hr))
{
    // Handle general success case.
}
else
{
    // Handle errors.
    printf("Error!\n"); 
}

Egyes HRESULT értékek a Windows egy adott funkciójára vagy alrendszerére vonatkoznak. A Direct2D grafikus API például a D2DERR_UNSUPPORTED_PIXEL_FORMAThibakódot határozza meg, ami azt jelenti, hogy a program nem támogatott képpontformátumot használt. A Windows dokumentációja gyakran felsorolja azokat a hibakódokat, amelyeket egy metódus visszaadhat. Ezeket a listákat azonban nem érdemes véglegesnek tekinteni. A metódusok mindig visszaadhatnak egy HRESULT értéket, amely nem szerepel a dokumentációban. Ismét használja a SIKERES és a SIKERTELEN makrókat. Ha egy adott hibakódot tesztel, adjon meg egy alapértelmezett esetet is.

if (hr == D2DERR_UNSUPPORTED_PIXEL_FORMAT)
{
    // Handle the specific case of an unsupported pixel format.
}
else if (FAILED(hr))
{
    // Handle other errors.
}

Hibakezelési minták

Ez a szakasz a COM-hibák strukturált kezelésére szolgáló néhány mintát mutat be. Minden minta rendelkezik előnyökkel és hátrányokkal. Bizonyos mértékig a választás ízlés kérdése. Ha egy meglévő projekten dolgozik, előfordulhat, hogy már rendelkezik olyan kódolási irányelvvel, amely egy adott stílust ír le. Függetlenül attól, hogy melyik mintát alkalmazza, a robusztus kód betartja a következő szabályokat.

  • Minden olyan metódus vagy függvény esetében, amely egy HRESULTad vissza, ellenőrizze a visszatérési értéket a folytatás előtt.
  • Erőforrások felszabadítása használat után.
  • Ne próbáljon meg hozzáférni érvénytelen vagy nem inicializált erőforrásokhoz, például NULL mutatóihoz.
  • A kiadás után ne próbáljon meg erőforrást használni.

Ezeket a szabályokat szem előtt tartva az alábbi négy minta használható a hibák kezelésére.

Beágyazott if-ek

Minden olyan hívás után, amely egy HRESULTad vissza, használjon az if utasítást a sikeresség teszteléséhez. Ezután helyezze a következő metódushívást a hatókörébe, a utasítás esetén. A if utasítások tetszőlegesen mélyen beágyazhatók, szükség szerint. A modul korábbi példakódjai mind ezt a mintát használták, de itt ismét a következő:

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

Előnye

  • A változók minimális hatókörrel deklarálhatók. A pItem például csak akkor deklarálódik, ha használatban van.
  • Minden egyes , ha utasításban bizonyos invariánsok igazak: az összes korábbi hívás sikeres volt, és az összes megszerzett erőforrás továbbra is érvényes. Az előző példában, amikor a program eléri a legbelső if utasítást, akkor mind a pItem, mind a pFileOpen ismert, hogy érvényesek.
  • Egyértelmű, hogy mikor szabadít fel felületmutatókat és más erőforrásokat. Az erőforrást a végén szabadítja fel, amennyiben a utasítás közvetlenül követi az erőforrást megszerző hívást.

Hátrányai

  • Néhány ember számára nehézséget okoz a mélyre ágyazott szerkezetek olvasása.
  • A hibakezelés más elágaztatási és hurkolási utasításokkal keveredik. Ez megnehezítheti az általános programlogika követését.

Kaszkádolt ifs

Minden metódushívás után használjon egy , ha utasítást a sikeresség tesztelésére. Ha a metódus sikeres, helyezze a következő metódushívást a blokkba, ha a feltétel teljesül. De ahelyett, hogy további ágyaz be, ha utasításokat, helyezzen minden további SIKERES tesztet az előző után, ha blokkot. Ha bármelyik módszer meghiúsul, a többi SIKERES tesztek egyszerűen meghiúsulnak, amíg el nem éri a függvény alját.

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

Ebben a mintában a függvény végén szabadít fel erőforrásokat. Hiba esetén előfordulhat, hogy egyes mutatók érvénytelenek lesznek, amikor a függvény kilép. A kiadási érvénytelen mutatóra hívása összeomlik a program (vagy rosszabb), ezért az összes mutatót inicializálnia kell, hogy NULL, és mielőtt felengedné őket, ellenőrizze, hogy NULL-e. Ez a példa a SafeRelease függvényt használja; az intelligens mutatók szintén jó választásnak számítanak.

Ha ezt a mintát használja, óvatosnak kell lennie a ciklusszerkezetekkel. A cikluson belül szakítsa meg a ciklust, ha bármilyen hívás meghiúsul.

Előnye

  • Ez a minta kevesebb beágyazást hoz létre, mint a "beágyazott if feltételek" minta.
  • Az általános vezérlési folyamat könnyebben látható.
  • Az erőforrások a kód egy pontján jelennek meg.

Hátrányai

  • Minden változót deklarálni és inicializálni kell a függvény tetején.
  • Ha egy hívás meghiúsul, a függvény több szükségtelen hibaellenőrzést végez ahelyett, hogy azonnal kilépne a függvényből.
  • Mivel a vezérlés folyamata egy hiba után is folytatódik a függvényen keresztül, óvatosnak kell lennie a függvény teljes törzsében, hogy ne férjen hozzá érvénytelen erőforrásokhoz.
  • A cikluson belüli hibák speciális esetet igényelnek.

Ugrás sikertelenre

Minden metódushívás után ellenőrizze, hogy nem történt-e hiba. Hiba esetén ugorjon egy címkére a függvény alján. A címke után, de a függvényből való kilépés előtt engedje fel az erőforrásokat.

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

Előnye

  • Az általános vezérlési folyamat könnyen áttekinthető.
  • Ha a címkére nem ugrott, akkor garantált, hogy a kód minden pontján a FAILED ellenőrzést követően az összes korábbi hívás sikeres volt.
  • Az erőforrások a kódban egy helyen szabadulnak fel.

Hátrányai

  • Minden változót deklarálni és inicializálni kell a függvény tetején.
  • Egyes programozók nem szeretik goto használni a kódjukban. (Meg kell azonban jegyezni, hogy a goto használata erősen strukturált; a kód soha nem ugrik ki az aktuális függvényhíváson.)
  • goto utasítások kihagyják az inicializálókat.

Sikertelen dobás

Ahelyett, hogy egy utalóra ugorjon, kivételt dobhat, ha egy metódus meghibásodik. Ez idiomatikusabb C++ stílust eredményezhet, ha a kivételmentes kód írásához használják.

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

Figyelje meg, hogy ez a példa a CComPtr osztályt használja a felületmutatók kezeléséhez. Ha a kód kivételeket jelez, általában a RAII (Resource Acquisition is Initialization) mintát kell követnie. Ez azt jelenti, hogy minden erőforrást egy olyan objektumnak kell felügyelnie, amelynek a destruktora garantálja az erőforrás megfelelő kiadását. Kivétel esetén a destruktor meghívása garantált. Ellenkező esetben előfordulhat, hogy a program kiszivárogtatja az erőforrásokat.

Előnye

  • Kompatibilis a kivételkezelést használó meglévő kóddal.
  • Kompatibilis a kivételeket okozó C++ kódtárakkal, például a standard sablontárral (STL).

Hátrányai

  • Az erőforrások, mint például a memória vagy a fájlleírók kezeléséhez C++ objektumokra van szükség.
  • A kivételmentes kód írásához alapos ismeretekre van szükség.

Következő

3. modul. Windows-ábrák