0

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.

Zyl
  • 2,690
  • 2
  • 22
  • 27
  • 7
    You should catch the exception and return a code, because there's no telling what language the code that links to your DLL is written in. – Spencer Oct 05 '22 at 20:51
  • 11
    This isn't just a *"should"*, it's an absolute MUST. It's part of the COM contract. Exceptions must not ever cross the ABI (it might be called from C code after all). – IInspectable Oct 05 '22 at 20:53
  • 8
    Just to make sure, you need to catch *all* C++ exceptions, not just `std::bad_alloc`. To get the tedious translation between C++ exceptions and `HRESULT` values (for known exceptions anyway) out of the way, the WIL provides [exception guards](https://github.com/microsoft/wil/wiki/Error-handling-helpers#exception-guards). Similarly, C++/WinRT has [`to_hresult`](https://learn.microsoft.com/en-us/uwp/cpp-ref-for-winrt/error-handling/to-hresult) that ultimately does the same. – IInspectable Oct 05 '22 at 21:14
  • 4
    A COM contract such as IThumbnailProvider is not "the Windows API", it's "just" a documented binary contract (based on vtables, etc.) so it's language agnostic, it's not necessarily C. A calling language may have no idea of things such as "C++ exception" and, depending on how your compiler implements these exceptions, will probably crash due to a generated low-level OS error/exception (unless it itself tries/catches somehow). The only contractual way of returning an error from COM interfaces is to return an HRESULT if the method defines one. – Simon Mourier Oct 05 '22 at 21:38

1 Answers1

1

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.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • What leaves me confused is how I can figure out which language features violate the contract and which ones don't. For example, `noexcept` itself cannot be omitted in the method declaration when it is going to be part of the method definition, which afaik means it is part of the method signature and thus makes it look like it could somehow affect the ABI. (there might exist a better example to convey my point) – Zyl Oct 06 '22 at 12:53
  • 1
    `noexcept` is odd. It wasn't part of a function's type when introduced in C++11. Starting with C++17 it is part of the function's type. I suppose that's what you get when your entire programming languages is built around the wrong defaults... Regardless, what matters to COM is the raw function pointer (table), plus the calling convention used to call through those pointers. That's essentially the ABI contract. The link "Component Object Model" goes over the language requirements (3rd paragraph) which is another way of expressing the contractual rules. Does that make sense? – IInspectable Oct 09 '22 at 07:44