Home > Blockchain >  When WINAPI calls my code and an exception is thrown, should I catch it and return an HRESULT instea
When WINAPI calls my code and an exception is thrown, should I catch it and return an HRESULT instea

Time:10-07

I have implemented IThumbnailProvider which gets compiled to a dll and then registered using regsvr32.

Within the code, I make use of STL containers such as std::vector:

std::vector<double> someRGBAccumulatorForDownsamplingToThumbnail = std::vector<double>(1234567);

Because the STL containers are largely built around RAII, there is no null-checking for failed memory allocations. Instead, the above code will throw an exception in an out-of-memory scenario. When this happens, should I catch this exception to return an HRESULT (context: implementation of GetThumbnail) instead?

try {
    // ...
} catch (bad_alloc& ex) {
    return E_OUTOFMEMORY;
}

Or can WINAPI safely handle me allowing the exception to "bubble up"?

I am asking because I am reading that WINAPI is C-based, and that C does not have exceptions.

CodePudding user response:

IThumbnailProvider is a COM interface. The Component Object Model is a language-agnostic protocol that describes (among others) the binary contract between clients and implementers of interfaces. It establishes a boundary (the Application Binary Interface, ABI) with clear rules1.

Since the protocol is language-agnostic, things that are allowed to cross the ABI are limited to the least common denominator. It's ultimately slightly less than what C function calls support. Any language-specific construct (such as C exceptions) must not cross the ABI.

When implementing a COM interface in C you have to make sure that C exceptions never cross the ABI. The bare minimum you could do is mark all interface methods as noexcept:

HRESULT MyThumbnailProvider::GetThumbnail(UINT, HBITMAP*, WTS_ALPHATYPE*) noexcept {
    // ...
}

While that meets all requirements of the COM contract, it's generally not desirable to have an uncaught exception bring down the entire process in which the COM object lives.

A more elaborate solution would instead catch all exceptions and turn them into HRESULT error codes (see Error Handling in COM), similar to what the code in question does:

HRESULT MyThumbnailProvider::GetThumbnail(UINT, HBITMAP*, WTS_ALPHATYPE*) noexcept {
    try {
        // ...
    } catch(...) {
        return E_FAIL;
    }
}

Again, this is perfectly valid, though any COM developer dreads seeing the 0x80004005 error code, that's semantically equivalent to "something went wrong". Hardly useful when trying to diagnose an issue.

A more helpful implementation would attempt to map certain well-known C exception types to standard HRESULT values (e.g. std::bad_alloc -> E_OUTOFMEMORY, or std::system_error to the result of calling HRESULT_FROM_WIN32). While one could manually implement a catch-cascade on every interface method implementation, there are libraries that do it for you already. The Windows Implementation Library (WIL) provides exception guards for this purpose, keeping the details out of your code.

The following is a possible interface method implementation using the WIL:

HRESULT MyThumbnailProvider::GetThumbnail(UINT, HBITMAP*, WTS_ALPHATYPE*) noexcept {
    try {
        // ...
    }
    CATCH_RETURN();
}

As an aside, I've kept the noexcept specifiers on the latter two implementations as a defensive measure only; they are not strictly required, but keep the interface valid in case the implementation changes in the future in a way that would allow a C exception to escape.


1 I'm not aware of an official document that spells out those rules. We have to assume that the compiler is the specification. Incidentally, Microsoft's C and C compiler do not agree, which the Direct2D team found out the hard way.

  • Related