Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом разделе рассматриваются стратегии обработки ошибок при программировании с помощью C++/WinRT. Дополнительные сведения и фоновые сведения см. в статьях Об ошибках и обработке исключений (современный C++).
Избегайте перехвата и генерации исключений
Мы рекомендуем вам продолжать писать код, безопасный с точки зрения исключений, но по возможности избегать перехвата и генерации исключений. Если для исключения нет обработчика, Windows автоматически формирует отчет об ошибке (включая минидамп сбоя), который поможет вам определить, где именно возникла проблема.
Не генерируйте исключение, которое собираетесь перехватить. И не используйте исключения для ожидаемых сбоев. Создает исключение только в том случае, если возникает непредвиденная ошибка среды выполнения, и обрабатывает все остальное с помощью кодов ошибок и результатов напрямую и близко к источнику сбоя. Таким образом, при возникновении исключения вы знаете, что причина является ошибкой в коде или исключительным состоянием ошибки в системе.
Рассмотрим сценарий доступа к реестру Windows. Если приложению не удается прочитать значение из реестра, это нормально, и такую ситуацию следует корректно обработать. Не выбрасывайте исключение; вместо этого возвращайте значение bool или enum, указывающее, что значение не удалось прочитать и, возможно, почему. С другой стороны, если не удается записать значение в реестр, это, скорее всего, указывает на более серьёзную проблему, с которой ваше приложение не сможет разумно справиться. В таком случае вы не хотите, чтобы ваше приложение продолжало работать, поэтому исключение, которое приводит к возникновению отчета об ошибке, является самым быстрым способом, чтобы ваше приложение не причиняли никакого вреда.
Например, попробуйте получить изображение эскиза из вызова StorageFile.GetThumbnailAsync, а затем передать этот эскиз в BitmapSource.SetSourceAsync. Если эта последовательность вызовов приводит к тому, что вы передаёте nullptr в SetSourceAsync (файл изображения не удаётся прочитать; возможно, из-за расширения кажется, что он содержит данные изображения, хотя на самом деле это не так), то будет создано исключение недопустимого указателя. Если вы обнаружите такой случай в своём коде, то вместо того, чтобы перехватывать и обрабатывать его как исключение, проверьте, возвращает ли GetThumbnailAsync значение nullptr.
Создание исключений, как правило, медленнее, чем использование кодов ошибок. Если вы генерируете исключение только при возникновении критической ошибки, то при нормальном ходе работы вам никогда не придется расплачиваться снижением производительности.
Но более вероятный удар по производительности включает в себя затраты на время выполнения, гарантируя, что соответствующие деструкторы вызываются в маловероятном случае возникновения исключения. Стоимость этой гарантии возникает, возникает ли исключение на самом деле или нет. Таким образом, необходимо убедиться, что компилятор имеет хорошее представление о том, какие функции могут потенциально вызывать исключения. Если компилятор может доказать, что из некоторых функций ( noexcept спецификации) не будет никаких исключений, то он может оптимизировать создаваемый код.
Перехват исключений
Условие ошибки, возникающее на уровне среда выполнения Windows ABI, возвращается в виде значения HRESULT. Но вам не нужно обрабатывать HRESULTs в коде. Код проекции C++/WinRT, создаваемый для API на стороне клиента, обнаруживает ошибочный код HRESULT на уровне ABI и преобразует его в исключение winrt::hresult_error, которое можно перехватить и обработать. Если вы хотите обрабатывать HRESULTs, используйте тип winrt::hresult .
Например, если пользователь удаляет изображение из библиотеки рисунков во время итерации приложения по этой коллекции, проекция создает исключение. И это тот случай, когда вам придётся перехватить и обработать это исключение. Ниже приведен пример кода, показывающий этот случай.
#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. Еще один пример такого преобразования HRESULT в исключение: если API компонента возвращает E_OUTOFMEMORY, генерируется исключение std::bad_alloc.
Предпочитайте winrt::hresult_error::code, если вам нужно лишь посмотреть код HRESULT. Функция winrt::hresult_error::to_abi с другой стороны преобразуется в объект ошибки COM и отправляет состояние в локальное хранилище COM-потока.
Создание исключений
В случаях, когда вы решите, что, если вызов данной функции завершится сбоем, приложение не сможет восстановиться (вы больше не сможете полагаться на нее для работы прогнозно). В приведенном ниже примере кода используется значение winrt::handle в качестве оболочки вокруг HANDLE, возвращаемого из CreateEvent. Затем он передает дескриптор (создавая из него значение bool) шаблонной функции winrt::check_bool.
winrt::check_bool работает с bool, или с любым значением, которое можно преобразовать в false (условие ошибки) или true (условие успеха).
winrt::handle h{ ::CreateEvent(nullptr, false, false, nullptr) };
winrt::check_bool(bool{ h });
winrt::check_bool(::SetEvent(h.get()));
Если значение, передаваемое в winrt::check_bool , равно false, происходит следующая последовательность действий.
- winrt::check_bool вызывает функцию winrt::throw_last_error .
- winrt::throw_last_error вызывает GetLastError , чтобы получить значение кода последней ошибки вызывающего потока, а затем вызывает функцию winrt::throw_hresult .
- winrt::throw_hresult создает исключение с помощью объекта winrt::hresult_error (или стандартного объекта), представляющего этот код ошибки.
Поскольку API Windows сообщают об ошибках времени выполнения с помощью различных типов возвращаемых значений, помимо winrt::check_bool существует несколько других полезных вспомогательных функций для проверки значений и генерации исключений.
- winrt::check_hresult. Проверяет, представляет ли код HRESULT ошибку и, если да, вызывает winrt::throw_hresult.
- winrt::check_nt. Проверяет, представляет ли код ошибку и, если да, вызывает winrt::throw_hresult.
- winrt::check_pointer. Проверяет, имеет ли указатель значение NULL и, если да, вызывает winrt::throw_last_error.
- winrt::check_win32. Проверяет, представляет ли код ошибку и, если да, вызывает winrt::throw_hresult.
Эти вспомогательные функции можно использовать для распространенных типов кода возврата или ответить на любое условие ошибки и вызвать winrt::throw_last_error или winrt::throw_hresult.
Создание исключений при создании API
Все границы двоичного интерфейса приложения среда выполнения Windows (или границы ABI) должны быть объявлены как noexcept — то есть исключения никогда не должны выходить за их пределы. При создании API всегда следует пометить границу ABI ключевым словом C++ noexcept .
noexcept имеет определенное поведение в C++. Если исключение C++ пересекает границу noexcept, процесс будет немедленно аварийно завершён с помощью std::terminate. Это поведение обычно желательно, так как необработанное исключение почти всегда подразумевает неизвестное состояние в процессе.
Так как исключения не должны пересекать границу ABI, условие ошибки, возникающее в реализации, возвращается на уровне ABI в виде кода ошибки HRESULT. При создании API с помощью C++/WinRT для вас генерируется код, который преобразует любое исключение, которое вы всё же создаёте в своей реализации, в HRESULT. Функция winrt::to_hresult используется в этом сгенерированном коде по следующему шаблону.
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, и winrt::hresult_error и производных типов. В реализации следует предпочесть winrt::hresult_error или производный тип, чтобы потребители API получали подробные сведения об ошибках. std::exception (которому соответствует E_FAIL) поддерживается в случаях, когда исключения возникают при использовании стандартной библиотеки шаблонов.
Отладка с noexcept
Как мы упоминали выше, если исключение C++ достигает границы noexcept, происходит немедленное аварийное завершение через std::terminate. Это не лучший вариант для отладки, потому что std::terminate часто приводит к потере значительной части или всей информации об ошибке либо контекста выброшенного исключения, особенно при использовании корутин.
Таким образом, в этом разделе рассматривается случай, когда метод ABI (который вы правильно аннотировали с noexcept) используется co_await для вызова асинхронного кода проекции C++/WinRT. Рекомендуется упаковать вызовы в код проекции C++/WinRT в winrt::fire_and_forget. Это обеспечивает надлежащее место, где необработанное исключение может быть корректно зарегистрировано как отложенное исключение, что значительно упрощает отладку.
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 вспомогательный метод, который вызывает winrt::terminate, который в свою очередь вызывает RoFailFastWithErrorContext. Это гарантирует, что вся контекстная информация (сохранённое исключение, код ошибки, сообщение об ошибке, трассировка стека и т. д.) сохраняется либо для интерактивной отладки, либо для посмертного дампа. Для удобства можно вынести часть fire-and-forget в отдельную функцию, которая возвращает winrt::fire_and_forget, а затем вызвать её.
Синхронный код
В некоторых случаях ваш метод ABI (который, опять же, вы правильно аннотировали с помощью noexcept) вызывает только синхронный код. Другими словами, co_await никогда не используется ни для вызова асинхронного метода среда выполнения Windows, ни для переключения между основным и фоновым потоками. В этом случае подход fire_and_forget по-прежнему будет работать, но он неэффективен. Вместо этого вы можете сделать что-то подобное.
HRESULT abi() noexcept try
{
// ABI code goes here.
} catch (...) { winrt::terminate(); }
Сбой быстро
Код в предыдущем разделе по-прежнему завершается сбоем. Как записано, этот код не обрабатывает никаких исключений. Любое необработанное исключение приводит к прекращению программы.
Но эта форма лучше, так как она обеспечивает возможность отладки. В редких случаях может потребоваться try/catchи обрабатывать определенные исключения. Но это должно быть редко, так как в этом разделе объясняется, мы не рекомендуем использовать исключения в качестве механизма управления потоками для ожидаемых условий.
Помните, что это плохая идея позволить необработанным исключениям избежать голого noexcept контекста. В таком случае среда выполнения C++ std::terminate процесс, в результате чего будет потеряна вся информация об отложенных исключениях, которую C++/WinRT тщательно сохраняет.
Assertions
Для внутренних предположений в приложении существуют утверждения. По возможности предпочитайте static_assert для проверки во время компиляции. Для условий времени выполнения используйте WINRT_ASSERT с логическим выражением.
WINRT_ASSERT — это определение макроса, и оно расширяется до _ASSERTE.
WINRT_ASSERT(pos < size());
WINRT_ASSERT не включается в релизные сборки; в отладочной сборке приложение останавливается в отладчике на строке кода, где находится проверка WINRT_ASSERT.
В деструкторах не следует использовать исключения. Таким образом, по крайней мере в отладочных сборках можно проверять результат вызова функции в деструкторе с помощью WINRT_VERIFY (принимающего логическое выражение) и WINRT_VERIFY_ (принимающего ожидаемый результат и логическое выражение).
WINRT_VERIFY(::CloseHandle(value));
WINRT_VERIFY_(TRUE, ::CloseHandle(value));
Важные API
- Шаблон функции winrt::check_bool
- функция winrt::check_hresult
- Шаблон функции winrt::check_nt
- Шаблон функции winrt::check_pointer
- Шаблон функции winrt::check_win32
- структура winrt::handle
- структура winrt::hresult_error
- функция winrt::throw_hresult
- функция winrt::throw_last_error
- функция winrt::to_hresult
Связанные темы
Windows developer