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?

有帮助吗?

解决方案

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 HRESULTs 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.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top