January 15th, 2024

C++/WinRT gotcha: Not all exceptions derive from hresult_error

I often see code that tries to catch all C++/WinRT exceptions by doing a

try {
    ... C++/WinRT code ...
} catch (winrt::hresult_error const& ex) {
    ... caught all C++/WinRT exceptions (right?) ...
}

Unfortunately, this doesn’t catch all C++/WinRT exceptions.

The code in C++/WinRT that converts HRESULTs to exceptions can be found in throw_hresult. From the code, you can see that every failure HRESULT turns into a thrown winrt::hresult_error, except for error_bad_alloc, which is the C++/WinRT internal name for E_OUT­OF­MEMORY.

Furthermore, your try block probably encompasses some C++ library code that could throw things like std::out_of_range, std::invalid_argument, or a plain old std::exception.

And of course if your code interacts with other libraries, you will want to catch the exceptions thrown by those other libraries, like the Windows Implementation Library.

If you want to catch all exceptions, then catch all exceptions. You can ask winrt::to_hresult() to convert the current exception to an HRESULT.

try {
    ... C++/WinRT code ...
} catch (...) {
    LogFailure(winrt::to_hresult());
}

In practice, catching std::bad_alloc doesn’t usually help much. Your exception-handling code is probably going to allocate some memory, so you’re back where you started.

Bonus chatter: One of the design principles of the Windows Runtime is that exceptions are intended to be used for unrecoverable errors. If there is a recoverable error, then it should be reported in a non-exceptional way. Some of the older Windows Runtime classes don’t follow this principle, but for the important ones, Windows has been slowly adding non-exceptional alternatives. For example, HttpClient.GetAsync now has a non-exceptional alternative HttpClient.TryGetAsync.

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

5 comments

Discussion is closed. Login to edit/delete existing comments.

  • Martin Ba

    Gotta chime in here on the

    > In practice, catching std::bad_alloc doesn’t usually help much. Your exception-handling code is probably going to allocate some memory, so you’re back where you started.

    which is not true at all in my experience: For me std::bad_alloc almost always comes from trying to allocate a "huge" buffer, or multiple "large" buffers.
    If I try to allocate a 655 MB std::vector and get a std::bad_alloc (had these a lot on 32bit because...

    Read more
    • Me Gusta · Edited

      There are two points to mention regarding this.

      1) How do you know from the std::bad_alloc handler that this is one of those "almost always" cases?
      2) What useful work can you do do in handling this error? Even if it is one of those cases where the allocation failed because of address space fragmentation, the allocation that caused the exception is still highly likely to fail again.

      While I understand that you could argue that part...

      Read more
      • NoLongerBreathedIn · Edited

        1) This is why std::bad_alloc should have a that you can get; for backwards-compatibility of source code, it would have to default to 0 (meaning "unknown").

        2) One case is when you're interpreting a script written in some other language, like ECMAScript or Lua. In that case, while you probably can't do much useful work in handling the error, if you know that the allocation failed because it was ludicrously large you can at least...

        Read more
  • George Tokmaji

    Nitpicks / additional information:

    > except for error_bad_alloc, which is the C++/WinRT internal name for E_OUT­OF­MEMORY.

    Which gets thrown as `std::bad_alloc`.

    > In practice, catching std::bad_alloc doesn’t usually help much.

    A common case would be catching it at the ABI boundary to convert them it into `E_OUTOFMEMORY` - at which point you're better off with `winrt::to_hresult` anyway, as it's less code, does the right thing and cleanly separates your own exception handling logic from the catch-all handler at ABI...

    Read more
  • Joshua Hudson

    Catching `std::bad_alloc` works fine if you don't have too many Microsoft libraries on the stack. The unwind code almost always frees enough memory to report out of memory. And in the rare case it doesn't, throwing a new `std::bad_alloc` is fine because there's another exception handler above it. The top-level worker has `catch(...)` and restores a sane working environment. Actions will work again as soon as enough memory becomes available. Most of the time the...

    Read more