Reputation: 43034
C++ exceptions can't cross COM module boundaries.
So, assume we are in a COM method body, and some C++ potentially-throwing method/function is called (this can throw because e.g. STL classes are used):
STDMETHODIMP CSomeComServer::DoSomething()
{
CppDoSomething(); // <--- This may throw C++ exceptions
return S_OK;
}
Q1. Is the above code a viable implementation?
For example, if that code is part of a context menu shell extension, if the C++ CppDoSomething()
function throws a C++ exception, what does Explorer do? Does it catch the C++ exception and unload the shell extension? Does it just crash Explorer (making it possible to analyze the problem using a crash dump) following a fail-fast approach?
Q2. Would an implementation like this be better?
STDMETHODIMP CSomeComServer::DoSomething()
{
//
// Wrap the potentially-throwing C++ code call in a safe try/catch block.
// C++ exceptions are caught and transformed to HRESULTs.
//
try
{
CppDoSomething(); // <--- This may throw C++ exceptions
return S_OK;
}
//
// Map C++ std::bad_alloc exception to E_OUTOFMEMORY HRESULT.
//
catch(const std::bad_alloc& ex)
{
// ... Log the exception what() message somewhere,
// e.g. using OutputDebugString().
....
return E_OUTOFMEMORY;
}
//
// Map C++ std::exception exception to generic E_FAIL.
//
catch(const std::exception& ex)
{
// ... Log the exception what() message somewhere,
// e.g. using OutputDebugString().
....
return E_FAIL;
}
}
Q3. Or would it be even better, if a C++ exception is thrown, to just set an internal flag (e.g. a bool m_invalid
data member) to put the COM server in a state such that it can't work anymore, so every successive call to its method returns some error code, like E_FAIL
or some other specific error?
Q4. Finally, assuming that Q2/Q3 are good implementation guidelines, it's possible to hide the verbose try/catch
guard in some convenient preprocessor macros (that can be reused in every COM method body), e.g.
#define COM_EXCEPTION_GUARD_BEGIN try \
{
#define COM_EXCEPTION_GUARD_END return S_OK; \
} \
catch(const std::bad_alloc& ex) \
{ \
.... \
return E_OUTOFMEMORY; \
} \
catch(const std::exception& ex) \
{ \
.... \
return E_FAIL; \
}
//
// May also add other mappings, like std::invalid_argument --> E_INVALIDARG ...
//
STDMETHODIMP CSomeComServer::DoSomething()
{
COM_EXCEPTION_GUARD_BEGIN
CppDoSomething(); // <--- This may throw C++ exceptions
COM_EXCEPTION_GUARD_END
}
STDMETHODIMP CSomeComServer::DoSomethingElse()
{
COM_EXCEPTION_GUARD_BEGIN
CppDoSomethingElse(); // <--- This may throw C++ exceptions
COM_EXCEPTION_GUARD_END
}
Using modern C++11/14, is it possible to replace the aforementioned preprocessor macros with something else, more convenient, more elegant, just better?
Upvotes: 6
Views: 438
Reputation: 170549
Never let exceptions propagate across COM boundary, otherwise the behavior is undefined and may include your C++ runtime terminate()
being called, the process going nuts and other nice bonuses. Just don't do it. Even if you "tested" it in some configuration - it's still undefined behavior and will silently break should minor environment or implementation changes occur.
You should catch and translate all C++ exceptions into HRESULT
s and optionally set IErrorInfo
with details. You can do so with macros wrapping each COM server method implementation or by copy-pasting this code everywhere - guess which is more maintainable.
The idea with driving the server into "invalid" state may make sense in some extreme situations but I can't imagine them at the moment. I guess it's not a universal solution. In general cases if you have exception safe code you shouldn't need this at all.
Upvotes: 4