I'm not asking if it is safe for a C++ exception to propagate through C code, nor what happens when such thing occurs. I have read the following questions in SO(1, 2, 3) and this FAQ. I'm asking how to proceed to :
Let me illustrate my idea :
Say libfoo
is a C library, that I want to use in my bar
C++ program. libfoo
needs a callback function foo_callback
that I must provide. The functions and methods used in my callback may throw an exception, so I wrote :
void my_callback(void)
{
try
{
// Do processing here.
}
catch(...)
{
// Catch anything to prevent an exception reaching C code.
// Fortunately, libfoo provides a foo_error function to
// signal errors and stop processing.
foo_error() ;
}
}
And then I use my callback as shown below :
// The bar program.
int main()
{
// Use libfoo function to set the desired callback
foo_set_callback(&my_callback) ;
// Start processing. This libfoo function uses internally my_callback.
foo_process() ;
// Check for errors
if( foo_ok() )
{
// Hurray !
}
else
{
// Something gone wrong.
// Unfortunately, we lost the exception that caused the error :(
}
}
What I want is to be able to catch the exceptions thrown from my_callback
in the main
function, without having the exception propagating through libfoo
(Yes, it's a sort of quantum exception experimenting quantum tunnelling through C code).
So the code I would love to use :
void my_callback(void)
{
try
{
// Do processing here.
}
catch(...)
{
// Catch anything to prevent an exception reaching C code.
// We save the exception using (the magic) ExceptionHolder.
ExceptionHolder::Hold() ;
// Call foo_error function to signal errors and stop processing.
foo_error() ;
}
}
// The bar program.
int main()
{
// Use libfoo function to set the desired callback
foo_set_callback(&my_callback) ;
try
{
// Start processing. This libfoo function uses internally my_callback.
foo_process() ;
// Once gone out of the C land, release any hold exception.
ExceptionHolder::Release() ;
}
catch(exception & e)
{
// Something gone wrong.
// Fortunately, we can handle it in some manner.
}
catch( /*something else */ )
{
}
// ...
}
Given the following constraints :
libfoo
is source-closed, written in C and provided in compiled format from a vendor. Tests conducted on the library showed that exceptions cannot propagate through it. I have no access to the source files nor I can obtain a compiled version that supports exceptions.My questions :
boost
), or even c++0x ?Many thanks for any advice !
EDIT : I added a response with a little implementation of the exception holding/releasing mechanism. All critics or propositions are welcome.
I believe that boost.exception has a mechanism that could be adapted to be useful for your purpose. See here for inspiration:
http://www.boost.org/doc/libs/1_47_0/libs/exception/doc/tutorial_exception_ptr.html
It seems to be intended for communicating their special exception type between threads, but I think it is pretty much the same thing - stop an exception from propagating across the thread boundary or C code boundary, stash a copy of it away behind a pointer, then retrieve it later via the pointer on the other side of the boundary and optionally rethrow it.
I'm not sure how feasible it is to make your own exceptions derive from boost's magical exception type, but if I remember correctly from tooling around with it a year or so ago, it's fairly reasonable.
In c++11 you can use current_exception() in your callback to set a variable of type exception_ptr, and then when your calling code is informed of the error by the library use rethow_exception().
This is the same mechanism used to propagate exceptions across threads in c++0x.
Example:
void foo(); // C++
extern "C" void bar(void *exp); // C
void baz(void *exp); // C++
void foo() {
std::exception_ptr exp;
bar(&exp);
if (exp) {
std::rethrow_exception(exp);
}
}
extern "C" void bar(void *exp) {
baz(exp);
}
void baz(void *exp) {
try {
// some code that might throw an exception
// ...
} catch (...) { // catch all exceptions, so as to avoid leaking any into the calling C code.
// capture the exception and make it available to the C++ code above the C code.
static_cast<std::exception_ptr*>(exp) = std::current_exception();
}
}
Edit : You can use fungo, a better implementation of the idea I described below. From its author :
fungo is a C++ library that is designed for those of us stuck using older C++ implementations that do not yet support std::exception_ptr.
In other words, fungo allows you to make a fair attempt at storing and later re-throwing exceptions caught by a catch(...) block. This is useful for propagating exceptions across thread-joins or over C/C++ boundary interfaces.
I'll mark this as an answer.
As I mentioned in my question, I cannot use C++0x/11 features for now (using new features isn't planned for now), and I will present here what I have done so far :
Exceptions have a lifetime that spans across the try-catcher block. In order to save an exception, one must create a copy on the heap. We get rid of the copy when re-throwing the exception. I wrote an exception holder interface :
class ExceptionHolderInterface
{
public :
ExceptionHolderInterface(void) ;
virtual ~ExceptionHolderInterface(void) ;
/* For holding an exception. To be called inside a catch block.*/
virtual void Hold(void) = 0 ;
/* For releasing an exception. To be called inside a try block.*/
virtual void Release(void) = 0 ;
private :
} ;
This is a type independent class. The exception type is introduced using templates :
template<typename ExceptionType>
class ExceptionHolder : public ExceptionHolderInterface
{
public :
ExceptionHolder(void) ;
virtual ~ExceptionHolder(void) ;
virtual void Hold(void)
{
try
{
throw ;
}
catch(const ExceptionType & e)
{
exception.reset(new ExceptionType(e)) ;
}
}
virtual void Release(void)
{
if(exception.get())
{
throw ExceptionType(*exception.get()) ;
}
}
private :
std::auto_ptr<ExceptionType> exception ;
// declare the copy-constructor and the assignment operator here to make the class non-copyable
} ;
I removed a bunch of tests/optimizations/verifications, I kept the main idea. So far we have an exception holder for one type, so we can build an exception store that can hold many types at a time.
class ExceptionStore
{
public :
ExceptionStore(void) ;
~ExceptionStore(void)
{
for(Iterator holder = exception_holders.begin() ; holder != exception_holders.end() ; ++holder)
{
delete (*holder) ;
}
}
// Add an exception type to handle
template<typename ExceptionType>
void AddExceptionHolder(void)
{
exception_holders.push_back(new ExceptionHolder<ExceptionType>()) ;
}
// Try to hold an exception using available holders. Use this inside a catch block.
void Hold(void)
{
Iterator holder = exception_holders.begin() :
while(holder != exception_holders.end())
{
try
{
(*holder)->Hold() ;
break ;
}
catch(...)
{
++holder ;
}
}
}
// Try to release any hold exception. Call this inside a try-block.
void Release(void)
{
Iterator holder = exception_holders.begin() :
while(holder != exception_holders.end())
{
(*holder++)->Release() ;
}
}
private :
std::list<ExceptionHolderInterface *> exception_holders ;
typedef std::list<ExceptionHolderInterface *>::iterator Iterator ;
// Declare the copy-constructor and the assignment operator here to make the class non-copyable
} ;
I can use the exception store as shown below :
// I made a global ExceptionStore just to keep the example simple.
ExceptionStore exception_store ;
void callable_from_c_code(void)
{
// Normally, we should retrieve the exception store in some manner.
try
{
// Do processing here. Exceptions may be thrown.
}
catch(...)
{
// Something wrong happened. Let's store the error for later.
exception_store.Hold() ;
}
// Exceptions do not propagate to C code.
}
int main(int, char * [])
{
// First, set the exception types we want to handle. The handling is done in
// the same order as below.
exception_store.AddExceptionHolder<std::runtime_error>() ;
exception_store.AddExceptionHolder<std::logic_error>() ;
exception_store.AddExceptionHolder<MyFancyException>() ;
// Somehow invoke some C code that uses `callable_from_c_code`
use_some_c_library_with_callback(&callable_from_c_code) ;
// Handle any caught exception
try
{
exception_holder.Release() ;
}
catch(std::exception &)
{
// Something gone wrong ...
}
catch(MyFancyException &)
{
// Nothing fancy despite the name. We have problems here ...
}
}
This is very basic, and there might be some unexpected scenarii that are not handled by this example. If an exception with a type not declared using AddExceptionHolder
is throw, you have two possibilities :
For now, I prefer using this solution to the more tested/used/verified boost::enable_current_exception because I can't afford refactoring the whole C++ code to surround all throw sites with boost::enable_current_exception(...)
.
Anyway, the std::exception_ptr
seems to be the perfect solution, and I will replace the above code once I can move to the new C++ standard.
If you are not up for a major refactor, are you able to identify a specific subset of exception types that might be thrown?
If so, you can probably write a container to store copies of each of these specifically, and then retrieve them on return from the C-call. This requires being able to copy the exception type.
Alternatively, consider what the ultimate resolution of the various exceptions is (if you can), and pack that up into some object for processing on return. This only works if you own enough of the codebase to know what potential handling the exceptions may generate.
A hydrid model would trap the exceptions, create a new exception based on what was found, and then throw that on return from the C lib.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With