Passing exceptions across a C API boundary
Asked Answered
H

3

10

I am writing a library in C++ which uses an older C API. The client of my library can specify callback functions, which are indirectly called through my library which is called through the C API. This means that all exceptions in the client callbacks must be handled.

My question is this: how can I catch the exception on one side of the boundary and re-throw it once the C API boundary has been recrossed and the execution is back in C++ land so that the exception can be handled by client code?

Hereinbefore answered 12/2, 2012 at 19:53 Comment(0)
I
5

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.

Infallibilism answered 12/2, 2012 at 20:2 Comment(3)
If the exception is thrown by value, will the exception_ptr keep it alive?Hereinbefore
@SethCarnegie: By the standard, yes. (§18.8.5/8 "The referenced object shall remain valid at least as long as there is an exception_ptr object that refers to it.") The Boost implementation should be doing the same.Infallibilism
This is awesome, it seems like the Committee designed C++11 with me in mind. It is also for the reason that VS2010 implements exception_ptr, current_exception and rethrow_exception that I can accept this answer. Thanks.Hereinbefore
H
3

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.

Henceforward answered 12/2, 2012 at 20:14 Comment(4)
This is a non-issue with Kenny TM's way because invoke_cxx_callback would return normally (having stored the exception in an exception_ptr) and when the C code returns to the C++ code, the C++ code would check if an exception was thrown and rethrow it if so.Hereinbefore
@SethCarnegie: I know, that's why I referred to both the other answers for "solutions". This answer only explains why there is no "pass through" of exceptions through C code.Appear
"you can have C++ exceptions implemented over Microsoft's structured exception handling" - That's not correct. Microsoft's compilers have always implemented C++ exceptions in terms of SEH exceptions. No choice there. The compiler switch merely controls the semantics of the exception handling keywords as implemented by MSC.Uredium
@Uredium I'm not sure that subtlety is important in this answer, but feel free to update the post to improve it :-)Appear
A
0

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.

Adjective answered 12/2, 2012 at 20:3 Comment(2)
I'd like not to lose the exception if possible though, for instance if the user's callback throws their own type of exception then I would like to throw it tooHereinbefore
@SethCarnegie if you're crossing over module boundaries (I assume you do, otherwise this whole thing wouldn't be an issue) then I don't see a way to preserve the actual exception information. A further complication is that C++ allows any type to be an exception so if you want to preserve it, you need to know what types to handle. If all exceptions are guaranteed to be std::exception derivatives, it may not be very hard but if any type is allowed, that changes things.Adjective

© 2022 - 2024 — McMap. All rights reserved.