Fitting COM into C++ System Error Handling
If you are programming on Windows, chances are high you are using COM. Be it for XML (MSXML), shell interaction, directshow, enumerating system devices or active directory objects, and a myriad of other functionality offered via COM interfaces.
One thing you have to deal with when using COM are return error codes of almost all interface methods, which they do in form of returning a HRESULT (typedef’d as long). An example of such would be:
Many times, those codes represent traditional Windows system error codes, and you can get to the windows error code via the HRESULT_CODE macro. That’s why in my opinion it makes sense to be able to turn them into system errors and simplify error handling.
Native C++ Compiler COM Support
I find it frightening when I see examples all over the internet, including MSDN, with C-style COM programming. All the object lifetime and error handling is done by hand, which clutters up the real task and understanding of what a COM library offers.
If you are like me, you really appreciate the Native C++ compiler COM support providing classes like _variant_t, _bstr_t, _com_ptr_t and _com_error. It’s a simple, thin and lightweight C++ wrapper around COM interfaces, which you don’t want to miss out. The C++ wrappers not only manage the lifetime of memory or objects but also simplify error handling, because you can wrap error codes in an error class and throw that around.
Unless you have to use the ‘raw’ COM interface methods, you get those wrappers for free when importing type libraries via VC’s #import directive.
Microsoft even updated _com_ptr_t with move semantics not so long ago, meaning it’s still mainstream.
#import’ing a type library usually creates wrapper methods around the raw functions, which perform the boiler plate of error checking
The wrapper checks for a FAILED operation and simply throws a _com_error, usually via a call to _com_issue_error_ex. E.g. the implementation of IXMLDOMParseError::get_line looks like this:
Simplify
_com_error is just one of many within the whole ecosystem of C++ error classes; dealing with an ever-growing list of error classes can make try/catch blocks a pain. If you have already your own application-wide error class you probably convert library errors into your own.
I prefer using standard components because the standard usually makes programming simpler. It turns out that std::system_error, available since C++11, is really useful for dealing with system-like errors, because it was designed with extensibility in mind.
COM even offers the option to set your own translation handler via _set_com_error_handler, so we can directly throw system errors and translating is encapsulated at one single point.
System Error
Polymorphic, Extensible Error Closure
The real power of std::system_error stems from the fact that it only provides certain logic to deal with error codes, but is otherwise agnostic to the kind of system error. It’s nothing more than a std exception class additionally storing an error code object. This error code object in turn holds an error number and a pointer to an error category.
I won’t go into further detail as Christopher Kohlhoff describes at length the rationale behind std::system_error and how to extend it with your own types on his blog. The bottom line is that the error category allows us to easily extend the system error handling.
Implementation
Outline
So what do we need?
An enum type classifying a COM error
An error category class capable of translating HRESULT codes into readable messages, and connecting COM error types to system error’s polymorphism.
Translation functions from COM error to system error; this would be also the point to capture any additional description provided by the COM layer, which isn’t available anymore at a later point.
Automatic translation
Code - Declarations
Step 1 - Classification
Step 2 - Error Category
Step 3 - Translation (manual)
Step 3a - Desktop Family Apps
Step 3b - Non-Desktop Apps
There’s no IErrorInfo interface available, see Implementation
Step 4 - Translation (automatic)
Step 4a - Desktop Family Apps
Step 4b - Non-Desktop Apps
Implementation
It’s almost this simple. Extracting the error code message (step 2) and error description (step 3) is a bit tricky, but not hard either.
Things to be aware of:
Take care about unicode-to-ANSI string conversion:
The presented approach utilizes _com_util::ConvertBSTRToString, which in turn utilizes WideCharToMultiByte with the system codepage CP_ACPMitigate reference-counting _bstr_t at the point when getting the description for the error message
Trim error description at end-of-sentence (that’s because the description is prepended to the error code message).
The IErrorInfo interface isn’t available in non-desktop environments. It gets replaced by a unicode C-String.
There’s a preprocessor macro _COMDEF_NOT_WINAPI_FAMILY_DESKTOP_APP that we can use to tell the difference.
I think I thought about every detail for a correct, concise and convenient usage. Let’s take a look at some code again. Note that using namespace std; is assumed for readability.
Code - Implementation
Step 1 - Classification
Step 2 - Error Category
Step 3 - Translation (manual)
Step 3a - Desktop Family Apps
Step 3b - Non-Desktop Apps
Step 4 - Translation (automatic)
Step 4a - Desktop Family Apps
Step 4b - Non-Desktop Apps
Happy coding!
Klaus Triendl