C++/WinRT ile hata işleme

Bu konuda , C++/WinRT ile programlama sırasında hataları işleme stratejileri açıklanmaktadır. Daha fazla genel bilgi ve arka plan için bkz. Hatalar ve Özel Durum İşleme (Modern C++).

Özel durumları yakalamaktan ve fırlatmaktan kaçının

Özel durumlara karşı güvenli kod yazmaya devam etmenizi, ancak mümkün olduğunda özel durumları yakalayıp fırlatmaktan kaçınmayı tercih etmenizi öneririz. Bir özel durum için işleyici yoksa, Windows otomatik olarak bir hata raporu oluşturur (çökmenin mini dökümü de dahil); bu da sorunun kaynağını bulmanıza yardımcı olur.

Yakalayacağınızı beklediğiniz bir özel durumu fırlatmayın. Ve beklenen başarısızlıklar için istisnalar kullanmayın. Yalnızca beklenmeyen bir çalışma zamanı hatası oluştuğunda özel durum oluşturup hata/sonuç kodlarıyla diğer her şeyi doğrudan ve hatanın kaynağına yakın bir şekilde işleyin. Bu şekilde, bir özel durum oluştuğunda , nedeninin kodunuzda bir hata veya sistemdeki olağanüstü bir hata durumu olduğunu bilirsiniz.

Windows Kayıt Defteri'ne erişme senaryolarını göz önünde bulundurun. Uygulamanız Kayıt Defteri'nden bir değeri okuyamıyorsa bu beklenen bir durum olur ve bunu düzgün bir şekilde işlemeniz gerekir. Özel durum fırlatmayın; bunun yerine, değerin okunamadığını ve mümkünse nedenini belirten bir bool veya enum değeri döndürün. Öte yandan Kayıt Defteri'ne bir değer yazamamak , büyük olasılıkla uygulamanızda hassas bir şekilde işleyebileceğinden daha büyük bir sorun olduğunu gösterir. Böyle bir durumda uygulamanızın devam edebilmesini istemezsiniz, bu nedenle hata raporuyla sonuçlanır bir özel durum, uygulamanızın herhangi bir zarara neden olmasını engelleyebilecek en hızlı yoldur.

Başka bir örnek için StorageFile.GetThumbnailAsync çağrısından küçük resim görüntüsü almayı ve ardından bu küçük resmi BitmapSource.SetSourceAsync'e geçirmeyi göz önünde bulundurun. Bu çağrı dizisi nullptr geçirmenize neden oluyorsa (görüntü dosyası okunamıyorsa; dosya uzantısı görüntü verileri içeriyor gibi görünmesine neden oluyor, ancak aslında içermiyorsa), geçersiz bir işaretçi özel durumunun atılmasına neden olursunuz. Kodunuzda böyle bir durumla karşılaşırsanız, bu durumu bir özel durum olarak yakalayıp işlemek yerine, GetThumbnailAsync tarafından nullptr döndürülüp döndürülmediğini denetleyin.

Özel durumlar oluşturma işlemi, hata kodlarını kullanmaktan daha yavaş olma eğilimindedir. Yalnızca önemli bir hata oluştuğunda bir özel durum oluşturursanız, her şey yolunda giderse performans fiyatını asla ödemezsiniz.

Ancak daha olası bir performans kaybı, düşük olasılıkla bir istisna fırlatıldığında uygun yıkıcıların çağrılmasını sağlamanın çalışma zamanı ek yükünden kaynaklanır. Bu güvencenin maliyeti, bir özel durumun gerçekten oluşturup oluşturmamasından gelir. Bu nedenle, derleyicinin hangi işlevlerin özel durumlar oluşturabileceği konusunda iyi bir fikri olduğundan emin olmanız gerekir. Derleyici belirli işlevlerden ( noexcept belirtim) herhangi bir özel durum olmadığını kanıtlayabilirse, oluşturduğu kodu iyileştirebilir.

Özel durumları yakalama

Windows Çalışma Zamanı ABI katmanında ortaya çıkan bir hata koşulu HRESULT değeri biçiminde döndürülür. Ancak kodunuzda HRESULT'leri işlemeniz gerekmez. Tüketen tarafındaki bir API için oluşturulan C++/WinRT projeksiyon kodu, ABI katmanında bir hata HRESULT kodu algılar ve kodu yakalayıp işleyebileceğiniz bir winrt::hresult_error özel duruma dönüştürür. HRESULTs işlemek istiyorsanızwinrt::hresult türünü kullanın.

Örneğin, uygulamanız bu koleksiyon üzerinde yineleme yaparken kullanıcı Resim Kitaplığı'ndan bir görüntüyü silerse projeksiyon bir özel durum oluşturur. Bu, o istisnayı yakalayıp işlemeniz gereken bir durumdur. Burada bu olayı gösteren bir kod örneği verilmiş.

#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Microsoft.UI.Xaml.Media.Imaging.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Storage;
using namespace Microsoft::UI::Xaml::Media::Imaging;

IAsyncAction MakeThumbnailsAsync()
{
    auto imageFiles{ co_await KnownFolders::PicturesLibrary().GetFilesAsync() };

    for (StorageFile const& imageFile : imageFiles)
    {
        BitmapImage bitmapImage;
        try
        {
            auto thumbnail{ co_await imageFile.GetThumbnailAsync(FileProperties::ThumbnailMode::PicturesView) };
            if (thumbnail) bitmapImage.SetSource(thumbnail);
        }
        catch (winrt::hresult_error const& ex)
        {
            winrt::hresult hr = ex.code(); // HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND).
            winrt::hstring message = ex.message(); // The system cannot find the file specified.
        }
    }
}

co_await ile biten bir işlevi çağırırken aynı deseni bir eş yordamda kullanın. Bunun, HRESULT’nin özel duruma dönüştürülmesine ilişkin bir başka örneği de, bir bileşen API’si E_OUTOFMEMORY döndürdüğünde std::bad_alloc özel durumunun fırlatılmasıdır.

HRESULT koduna göz atarken winrt::hresult_error::code'yi tercih edin. Diğer yandan winrt::hresult_error::to_abi işlevi bir COM hata nesnesine dönüştürülür ve durumu COM iş parçacığı yerel depolama alanına iletir.

Özel durumlar oluşturma

Belirli bir işleve yaptığınız çağrının başarısız olması durumunda uygulamanızın kurtarılamayacağına (artık tahmin edilebilir bir şekilde çalışması için buna güvenemeyeceğiniz) karar verdiğiniz durumlar olacaktır. Aşağıdaki kod örneği, CreateEvent'ten döndürülen HANDLE çevresinde sarmalayıcı olarak winrt::handle değerini kullanır. Ardından tanıtıcıyı (ondan bir bool değeri oluşturarak) winrt::check_bool işlev şablonuna geçirir. winrt::check_bool, bir bool ile veya false'ye (hata koşulu) ya da true'e (başarı koşulu) dönüştürülebilen herhangi bir değerle çalışır.

winrt::handle h{ ::CreateEvent(nullptr, false, false, nullptr) };
winrt::check_bool(bool{ h });
winrt::check_bool(::SetEvent(h.get()));

winrt::check_bool'a geçirdiğiniz değer false ise, aşağıdaki eylem dizisi gerçekleşir.

  • winrt::check_boolwinrt::throw_last_error işlevini çağırır.
  • winrt::throw_last_error çağıran iş parçacığının son hata kodu değerini almak için GetLastError'ı çağırır ve ardından winrt::throw_hresult işlevini çağırır.
  • winrt::throw_hresult , bu hata kodunu temsil eden winrt::hresult_error nesnesini (veya standart bir nesneyi) kullanarak bir özel durum oluşturur.

Windows API’leri çalışma zamanı hatalarını çeşitli dönüş değeri türleri kullanarak bildirdiğinden, winrt::check_bool’e ek olarak, değerleri denetlemek ve özel durumlar fırlatmak için yararlı olan birkaç başka yardımcı işlev daha vardır.

  • winrt::check_hresult. HRESULT kodunun bir hatayı temsil edip etmediğini denetler ve öyleyse winrt::throw_hresult çağırır.
  • winrt::check_nt. Kodun bir hatayı temsil edip etmediğini denetler ve öyleyse winrt::throw_hresult çağırır.
  • winrt::check_pointer. İşaretçinin null olup olmadığını denetler ve öyleyse winrt::throw_last_error çağırır.
  • winrt::check_win32. Kodun bir hatayı temsil edip etmediğini denetler ve öyleyse winrt::throw_hresult çağırır.

Bu yardımcı işlevleri yaygın dönüş kodu türleri için kullanabilir veya herhangi bir hata koşuluna yanıt verebilir ve winrt::throw_last_error veya winrt::throw_hresult çağırabilirsiniz.

API tasarlarken özel durum fırlatma

Tüm Windows Çalışma Zamanı Uygulama İkili Arabirimi sınırları (veya ABI sınırları) noexcept olmalıdır; yani özel durumlar bu sınırların dışına asla taşmamalıdır. BIR API yazarken, ABI sınırını her zaman C++ noexcept anahtar sözcüğüyle işaretlemeniz gerekir. noexcept C++ içinde belirli bir davranışa sahiptir. Bir C++ özel durumu bir noexcept sınırını aşarsa, süreç derhal std::terminate ile sonlandırılır. Bu davranış genellikle tercih edilir; çünkü işlenmeyen bir özel durum, neredeyse her zaman işlemin bilinmeyen bir durumda olduğunu gösterir.

Özel durumların ABI sınırını aşmaması gerektiğinden, bir uygulamada ortaya çıkan bir hata koşulu HRESULT hata kodu biçiminde ABI katmanında döndürülür. C++/WinRT kullanarak bir API yazarken, uygulamanızda oluşturduğunuz özel durumları HRESULT'a dönüştürmeniz için kod oluşturulur. winrt::to_hresult işlevi, oluşturulan kodda bunun gibi bir düzende kullanılır.

HRESULT DoWork() noexcept
{
    try
    {
        // Shim through to your C++/WinRT implementation.
        return S_OK;
    }
    catch (...)
    {
        return winrt::to_hresult(); // Convert any exception to an HRESULT.
    }
}

winrt::to_hresult, std::exception ve winrt::hresult_error ile bundan türetilen türlerden türetilen özel durumları işler. Uygulamanızda, API'nizin tüketicilerinin zengin hata bilgileri alması için winrt::hresult_error veya türetilmiş bir türü tercih etmelisiniz. std::exception (E_FAIL ile eşlenen) Standart Şablon Kitaplığı kullanımınızdan kaynaklanan özel durumlar söz konusu olduğunda desteklenir.

Noexcept ile hata ayıklanabilirliği

Yukarıda da belirttiğimiz gibi, bir sınıra isabet eden bir noexcept C++ özel durumu std::terminate ile hızlı başarısız olur. Bu, hata ayıklama açısından ideal değildir çünkü std::terminate, özellikle korutinler söz konusu olduğunda, çoğu zaman hatanın veya fırlatılan özel durumun bağlamının büyük bir kısmını ya da tamamını kaybeder.

Bu bölüm, ABI yönteminizin (noexcept ile doğru şekilde ek açıklama eklediğiniz) asenkron C++/WinRT projeksiyon kodunu çağırmak için co_await kullandığı durumu ele alır. Çağrıları bir winrt::fire_and_forget içinde C++/WinRT projeksiyon koduna sarmalamanızı öneririz. Bunu yapmak, işlenmemiş bir özel durumun saklanmış özel durum olarak uygun şekilde kaydedilmesi için uygun bir yer sağlar; bu da hata ayıklanabilirliği büyük ölçüde artırır.

HRESULT MyWinRTObject::MyABI_Method() noexcept
{
    winrt::com_ptr<Foo> foo{ get_a_foo() };

    [/*no captures*/](winrt::com_ptr<Foo> foo) -> winrt::fire_and_forget
    {
        co_await winrt::resume_background();

        foo->ABICall();

        AnotherMethodWithLotsOfProjectionCalls();
    }(foo);

    return S_OK;
}

winrt::fire_and_forget, unhandled_exception çağrısı yapan yerleşik bir yöntem yardımcısı vardır ve bu da RoFailFastWithErrorContext'i çağırır. Bu, her türlü bağlamın (saklanmış özel durum, hata kodu, hata iletisi, yığın geri izlemesi vb.) ister canlı hata ayıklama ister çökme sonrası döküm için korunmasını garanti eder. Kolaylık sağlamak için, fire-and-forget bölümünü winrt::fire_and_forget döndüren ayrı bir işleve ayırabilir ve ardından bu işlevi çağırabilirsiniz.

Zaman uyumlu kod

Bazı durumlarda, ABI yönteminiz (yine, noexcept ile düzgün şekilde açıklama eklediğiniz yöntem) yalnızca senkron kod çağırır. Başka bir deyişle, ne zaman uyumsuz bir Windows Çalışma Zamanı yöntemini çağırmak ne de ön plan ve arka plan iş parçacıkları arasında geçiş yapmak için hiçbir zaman co_await kullanmaz. Bu durumda, fire_and_forget tekniği yine işe yarar, ancak pek verimli değildir. Bunun yerine, bunun gibi bir şey yapabilirsiniz.

HRESULT abi() noexcept try
{
    // ABI code goes here.
} catch (...) { winrt::terminate(); }

Hızlı başarısız ol

Önceki bölümdeki kod yine de hızlı başarısız oluyor. Bu kod, yazıldığını gibi hiçbir özel durumu işlemez. İşlenmeyen herhangi bir istisna programın sonlanmasına neden olur.

Ancak bu form, hata ayıklanabilirliği sağladığından üstündür. Ender durumlarda, try/catch ve belirli istisnaları ele almak isteyebilirsiniz. Ancak bu, nadiren gerekli olmalıdır; çünkü bu konuda açıklandığı gibi, öngördüğünüz durumlar için özel durumların bir akış denetimi mekanizması olarak kullanılmasını önermiyoruz.

İşlenmeyen bir istisnanın çıplak bir noexcept bağlamından dışarı taşmasına izin vermenin iyi bir fikir olmadığını unutmayın. Bu durumda C++ çalışma zamanı, işlemi std::terminate ile sonlandırır ve böylece C++/WinRT'nin dikkatle kaydettiği saklanmış özel durum bilgileri kaybolur.

Assertions

Uygulamanızdaki iç varsayımları doğrulamak için doğrulamalar vardır. Mümkün olduğunca derleme zamanı doğrulaması için static_assert tercih edin. Çalışma zamanı koşulları için Boole ifadesiyle kullanın WINRT_ASSERT . WINRT_ASSERT bir makro tanımıdır ve _ASSERTE genişletilir.

WINRT_ASSERT(pos < size());

WINRT_ASSERT sürüm derlemelerinde derlenmiştir; hata ayıklama derlemesinde, onay işleminin bulunduğu kod satırında hata ayıklayıcıda uygulamayı durdurur.

Destructor'larınızda istisna kullanmamalısınız. Yani, en azından hata ayıklama derlemelerinde, bir yıkıcı içinden yapılan işlev çağrısının sonucunu WINRT_VERIFY (mantıksal bir ifadeyle) ve WINRT_VERIFY_ (beklenen bir sonuç ve mantıksal bir ifadeyle) kullanarak doğrulayabilirsiniz.

WINRT_VERIFY(::CloseHandle(value));
WINRT_VERIFY_(TRUE, ::CloseHandle(value));

Önemli API'ler