Bagikan melalui


Penanganan Kesalahan di COM (Mulai menggunakan Win32 dan C++)

COM menggunakan nilai HRESULT untuk menunjukkan keberhasilan atau kegagalan metode atau panggilan fungsi. Berbagai header SDK menentukan berbagai konstanta HRESULT . Sekumpulan kode seluruh sistem umum didefinisikan dalam WinError.h. Tabel berikut menunjukkan beberapa kode pengembalian di seluruh sistem tersebut.

Terus-menerus Nilai numerik Deskripsi
E_ACCESSDENIED 0x80070005 Akses ditolak.
E_FAIL 0x80004005 Kesalahan yang tidak ditentukan.
E_INVALIDARG 0x80070057 Nilai parameter tidak valid.
E_OUTOFMEMORY 0x8007000E Kehabisan memori.
E_POINTER 0x80004003 NULL diberikan secara tidak tepat untuk nilai pointer.
E_UNEXPECTED 0x8000FFFF Kondisi tak terduga.
S_OK 0x0 Sukses.
S_FALSE 0x1 Sukses.

 

Semua konstanta dengan awalan "E_" adalah kode kesalahan. Konstanta S_OK dan S_FALSE keduanya adalah kode keberhasilan. Mungkin 99% metode COM mengembalikan S_OK ketika berhasil; tetapi jangan biarkan fakta ini menyesatkan Anda. Metode mungkin mengembalikan kode keberhasilan lainnya, jadi selalu uji kesalahan dengan menggunakan makro BERHASIL atau GAGAL. Contoh kode berikut menunjukkan cara yang salah dan cara yang tepat untuk menguji keberhasilan panggilan fungsi.

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

Kode keberhasilan S_FALSE perlu disebutkan. Beberapa metode menggunakan S_FALSE untuk berarti, kira-kira, kondisi negatif yang bukan kegagalan. Ini juga dapat menunjukkan "no-op"—metode tersebut berhasil, tetapi tidak memberikan efek apa pun. Misalnya, fungsi CoInitializeEx mengembalikan S_FALSE jika Anda memanggilnya untuk kedua kalinya dari utas yang sama. Jika Anda perlu membedakan antara S_OK dan S_FALSE dalam kode Anda, Anda harus menguji nilai secara langsung, tetapi masih menggunakan FAILED atau SUCCEEDED untuk menangani kasus yang tersisa, seperti yang ditunjukkan dalam contoh kode berikut.

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

Beberapa nilai HRESULT khusus untuk fitur atau subsistem Windows tertentu. Misalnya, API grafis Direct2D menentukan kode kesalahan D2DERR_UNSUPPORTED_PIXEL_FORMAT, yang berarti bahwa program menggunakan format piksel yang tidak didukung. Dokumentasi Windows sering memberikan daftar kode kesalahan tertentu yang mungkin dikembalikan oleh metode. Namun, Anda tidak boleh menganggap daftar ini definitif. Metode selalu dapat mengembalikan nilai HRESULT yang tidak tercantum dalam dokumentasi. Sekali lagi, gunakan makro BERHASIL dan GAGAL. Jika Anda menguji kode kesalahan tertentu, sertakan kasus default juga.

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

Pola untuk Penanganan Kesalahan

Bagian ini melihat beberapa pola untuk menangani kesalahan COM dengan cara terstruktur. Setiap pola memiliki kelebihan dan kekurangan. Sampai batas tertentu, pilihannya adalah masalah rasa. Jika Anda mengerjakan proyek yang ada, proyek tersebut mungkin sudah memiliki panduan pengodean yang memproskripsikan gaya tertentu. Terlepas dari pola mana yang Anda adopsi, kode yang kuat akan mematuhi aturan berikut.

  • Untuk setiap metode atau fungsi yang mengembalikan HRESULT, periksa nilai pengembalian sebelum melanjutkan.
  • Lepaskan sumber daya setelah digunakan.
  • Jangan mencoba mengakses sumber daya yang tidak valid atau tidak diinisialisasi, seperti pointer NULL .
  • Jangan mencoba menggunakan sumber daya setelah Anda merilisnya.

Dengan mengingat aturan ini, berikut adalah empat pola untuk menangani kesalahan.

Ifs berlapis

Setelah setiap panggilan yang mengembalikan HRESULT, gunakan pernyataan if untuk menguji keberhasilan. Kemudian, letakkan panggilan metode berikutnya dalam cakupan pernyataan if . Pernyataan if dapat disarangkan sesuai kebutuhan. Contoh kode sebelumnya dalam modul ini semuanya telah menggunakan pola ini, tetapi di sini lagi:

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

Kelebihan

  • Variabel dapat dideklarasikan dengan cakupan minimal. Misalnya, pItem tidak dideklarasikan sampai digunakan.
  • Dalam setiap pernyataan jika , invarian tertentu benar: Semua panggilan sebelumnya telah berhasil, dan semua sumber daya yang diperoleh masih valid. Dalam contoh sebelumnya, ketika program mencapai pernyataan if paling dalam, baik pItem maupun pFileOpen diketahui valid.
  • Jelas kapan harus merilis penunjuk antarmuka dan sumber daya lainnya. Anda merilis sumber daya di akhir pernyataan if yang segera mengikuti panggilan yang memperoleh sumber daya.

Kerugian

  • Beberapa orang menemukan struktur yang terlalu dalam sulit dibaca.
  • Penanganan kesalahan dicampur dengan pernyataan percabangan dan perulangan lainnya. Ini dapat membuat logika program keseluruhan lebih sulit diikuti.

Kaskading ifs

Setelah setiap panggilan metode, gunakan pernyataan if untuk menguji keberhasilan. Jika metode berhasil, tempatkan panggilan metode berikutnya di dalam blok if . Tetapi alih-alih menyusun pernyataan `if` secara bersarang lebih lanjut, tempatkanlah masing-masing pengujian BERHASIL setelah blok `if` sebelumnya. Jika ada metode yang gagal, semua pengujian BERHASIL yang tersisa langsung gagal sampai bagian bawah fungsi tercapai.

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

Dalam pola ini, Anda merilis sumber daya di akhir fungsi. Jika terjadi kesalahan, beberapa penunjuk mungkin tidak valid saat fungsi keluar. Memanggil Rilis pada pointer yang tidak valid akan merusak program (atau lebih buruk), jadi Anda harus menginisialisasi semua pointer ke NULL dan memeriksa apakah mereka NULL sebelum melepaskannya. Contoh ini menggunakan SafeRelease fungsi; penunjuk cerdas juga merupakan pilihan yang baik.

Jika Anda memakai pola ini, Anda harus berhati-hati dengan struktur perulangan. Di dalam perulangan, putuskan dari perulangan jika ada panggilan yang gagal.

Kelebihan

  • Pola ini menciptakan pola dengan bersarang yang lebih sedikit dibandingkan pola "if bertingkat".
  • Alur kontrol keseluruhan lebih mudah dilihat.
  • Sumber daya dirilis pada satu titik dalam kode.

Kerugian

  • Semua variabel harus dideklarasikan dan diinisialisasi di bagian atas fungsi.
  • Jika panggilan gagal, prosedur melakukan beberapa pemeriksaan kesalahan yang tidak perlu, alih-alih segera menghentikan prosedur.
  • Karena alur kontrol berlanjut melalui fungsi setelah kegagalan, Anda harus berhati-hati di seluruh isi fungsi untuk tidak mengakses sumber daya yang tidak valid.
  • Kesalahan di dalam perulangan memerlukan kasus khusus.

Bertindak Saat Gagal

Setelah setiap panggilan metode, uji kegagalan (tidak berhasil). Jika gagal, lompat ke sebuah label di dekat akhir fungsi. Setelah label, tetapi sebelum keluar dari fungsi, lepaskan sumber daya.

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

Kelebihan

  • Alur kontrol keseluruhan mudah dilihat.
  • Pada setiap titik dalam kode setelah pemeriksaan GAGAL, apabila Anda tidak melompat ke label, dijamin bahwa semua panggilan sebelumnya telah berhasil.
  • Sumber daya dirilis di satu tempat dalam kode.

Kerugian

  • Semua variabel harus dideklarasikan dan diinisialisasi di bagian atas fungsi.
  • Beberapa programmer tidak suka menggunakan goto dalam kode mereka. (Namun, perlu dicatat bahwa penggunaan goto ini sangat terstruktur; kode tidak pernah melompat di luar panggilan fungsi saat ini.)
  • pernyataan goto melewati penginisialisasi.

Lempar Jika Gagal

Daripada melompat ke label, Anda dapat melemparkan pengecualian ketika metode gagal. Ini dapat menghasilkan gaya C++ yang lebih idiomatik jika Anda terbiasa menulis kode yang aman dari pengecualian.

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

Perhatikan bahwa contoh ini menggunakan kelas CComPtr untuk mengelola penunjuk antarmuka. Umumnya, jika kode Anda melemparkan pengecualian, Anda harus mengikuti pola RAII (Akuisisi Sumber Daya adalah Inisialisasi). Artinya, setiap sumber daya harus dikelola oleh objek yang destruktornya menjamin bahwa sumber daya dirilis dengan benar. Jika pengecualian dilemparkan, destruktor dijamin akan dipanggil. Jika tidak, program Anda mungkin membocorkan sumber daya.

Kelebihan

  • Kompatibel dengan kode yang ada yang menggunakan penanganan pengecualian.
  • Kompatibel dengan pustaka C++ yang melemparkan pengecualian, seperti Pustaka Templat Standar (STL).

Kerugian

  • Memerlukan objek C++ untuk mengelola sumber daya seperti memori atau handel file.
  • Membutuhkan pemahaman yang baik tentang cara menulis kode yang aman pengecualian.

Berikutnya

Modul 3. Windows Grafik