Passing exceptions across a C API boundary

半城伤御伤魂 提交于 2019-11-30 13:04:20

With C++11 we could use:

std::exception_ptr active_exception;

try
{
    // call code which may throw exceptions
}
catch (...)
{
    // an exception is thrown. save it for future re-throwing.
    active_exception = std::current_exception();
}

// call C code
...

// back to C++, re-throw the exception if needed.
if (active_exception)
    std::rethrow_exception(active_exception);

Before C++11 these can still be used via Boost Exception.

André Caron

Some environments support this more or less directly.

For instance, if you enable structured exception handling and C++ exceptions through the /EH compiler switch, you can have C++ exceptions implemented over Microsoft's structured exception handling ("exceptions" for C). Provided these options are set when compiling all your code (the C++ at each end and the C in the middle) stack unwinding will "work".

However, this is almost always a Bad Idea (TM). Why, you ask? Consider that the piece of C code in the middle is:

WaitForSingleObject(mutex, ...);
invoke_cxx_callback(...);
ReleaseMutex(mutex);

And that the invoke_cxx_callback() (....drum roll...) invokes your C++ code that throws an exception. You will leak a mutex lock. Ouch.

You see, the thing is that most C code is not written to handle C++-style stack unwinding at any moment in a function's execution. Moreover, it lacks destructors, so it doesn't have RAII to protect itself from exceptions.

Kenny TM has a solution for C++11 and Boost-based projects. xxbbcc has a more general, albeit more tedious solution for the general case.

You can probably pass a structure across the C interface that gets filled out with error information in case of an exception and then when that is received on the client side, check it and throw an exception inside the client, based on data from the structure. If you only need minimal information to recreate your exception, you can probably just use a 32-bit/64-bit integer as an error code. For example:

typedef int ErrorCode;

...

void CMyCaller::CallsClient ()
{
    CheckResult ( CFunction ( ... ) );
}

void CheckResult ( ErrorCode nResult )
{
    // If you have more information (for example in a structure) then you can
    // use that to decide what kind of exception to throw.)
    if ( nResult < 0 )
        throw ( nResult );
}

...

// Client component's C interface

ErrorCode CFunction ( ... )
{
    ErrorCode nResult = 0;

    try
    {
        ...
    }
    catch ( CSomeException oX )
    {
        nResult = -100;
    }
    catch ( ... )
    {
        nResult = -1;
    }

    return ( nResult );
}

If you need more information than a single int32/int64 then you can allocate a structure before the call and pass its address to the C function which will, in turn, catch exceptions internally and if they happen, throws an exception on its own side.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!