Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ potentially-throwing code at COM method boundaries

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?

like image 948
Mr.C64 Avatar asked Oct 21 '22 03:10

Mr.C64


1 Answers

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.

like image 185
sharptooth Avatar answered Oct 27 '22 10:10

sharptooth