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?

DMK
  • 2,448
  • 1
  • 24
  • 35
Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249

3 Answers3

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.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • If the exception is thrown by value, will the `exception_ptr` keep it alive? – Seth Carnegie Feb 12 '12 at 20:04
  • @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. – kennytm Feb 12 '12 at 20:09
  • 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. – Seth Carnegie Feb 12 '12 at 20:22
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.

Community
  • 1
  • 1
André Caron
  • 44,541
  • 12
  • 67
  • 125
  • 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. – Seth Carnegie Feb 12 '12 at 20:31
  • @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. – André Caron Feb 12 '12 at 20:46
  • *"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. – IInspectable May 14 '16 at 22:11
  • @IInspectable I'm not sure that subtlety is important in this answer, but feel free to update the post to improve it :-) – André Caron May 27 '16 at 17:22
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.

xxbbcc
  • 16,930
  • 5
  • 50
  • 83
  • 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 too – Seth Carnegie Feb 12 '12 at 20:07
  • @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. – xxbbcc Feb 12 '12 at 20:14