After a long hiatus, I wrote a bit of code in C++ over the last two days. While I haven’t yet internalized a set of rules for error handling in C++ (see my previous posts about error handling A C/C++ Error Handling Discipline: Part 1, A C/C++ Error Handling Discipline: Part 2, A C/C++ Error Handling Discipline: Part 3, and On C++ Error Handling), I decided to use C++ exceptions in this application. Furthermore, since I was using MSXML, a lot of the error handling information was contained in IErrorInfo objects. To this end, I wrote the following macro:
#define ThrowComErrorIfFailed(x) \\
do { \\
HRESULT hr = (x); \\
if (FAILED(hr)) { \\
CComPtr<IErrorInfo> spErrorInfo; \\
BOOL hasErrorInfo = (::GetErrorInfo(0, &spErrorInfo) == S_OK); \\
_com_raise_error(hr, hasErrorInfo ? spErrorInfo : 0); \\
} \\
} while (0)
This macro uses the GetErrorInfo() function to retrieve the IErrorInfo object and _com_raise_error() to throw a _com_error class as the exception. (By the way, this macro can—and probably should—be written as an inline function. Old habits die hard, I guess.)
When I first wrote this macro, I thought it might be general enough to use in any COM application. Unfortunately, I was wrong. There’s no guarantee that the GetErrorInfo() call corresponds to the most recent COM call, as there’s no guarantee that it will be cleared or reset if the COM call doesn’t support IErrorInfo. A proper macro would first test if the COM object supports IErrorInfo by querying for ISupportErrorInfo and calling ISupportErrorInfo::InterfaceSupportsErrorInfo(). Only if these tests succeed would the macro then proceed to call GetErrorInfo().
To this end, I wrote the following code:
void ThrowComErrorIfFailed
(
HRESULT hr,
IUnknown* pUnk,
REFIID iid
)
{
if (FAILED(hr)) {
bool hasErrorInfo = false;
CComPtr<IErrorInfo> spErrorInfo;
if (pUnk)
{
CComQIPtr<ISupportErrorInfo> spSupportErrorInfo(pUnk);
if (spSupportErrorInfo) {
if (spSupportErrorInfo->InterfaceSupportsErrorInfo(iid) == S_OK) {
if (::GetErrorInfo(0, &spErrorInfo) == S_OK) {
hasErrorInfo = true;
}
}
}
}
_com_raise_error(hr, hasErrorInfo ? spErrorInfo : 0);
}
}
Note that to use this function, you must provide the COM object upon which you made the call (pUnk) and the exact interface of the object (iid). Because I used ATL’s CComPtr extensively, I also wrote the following utility function:
template <class T>
void ThrowComErrorIfFailed
(
HRESULT hr,
const CComPtr<T>& comPtr
)
{
ThrowComErrorIfFailed(hr, comPtr, __uuidof(T));
}
This experience led me to the following observation: In order to correctly use IErrorInfo, you must know what object generated it. This means that if you call a COM object which implements ISupportErrorInfo, the call fails, and you return only the HRESULT failure code, you have lost the IErrorInfo information completely. Yet another reason to use exceptions, I guess…
Recent Comments