12

I'm implementing callbacks in C++ which will be called from ordinary C code. My main() function is C++ already, but C code will be responsible for creating threads that will eventually call my callbacks.

Right now my callbacks look like

int handle_foo(void *userdata) {
    try {
        MyCPPClass *obj = static_cast<MyCPPClass *>(userdata);
        obj->doStuff();
        return 0; // no error
    } catch(...) {
        LogError("doStuff failed"); 
        return -1; // error
    }
}

This works OK, but it seems weird to me. Furthermore, I lose some useful features such as the ability to find out what was thrown (without adding huge amounts of extra catch statements to each and every one of my callbacks).

Is try {} catch(...) {} here reasonable, or is there a better way to write my C callbacks?

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • If the event that `doStuff()` throws an exception, if there anything that your calling C program can actually do to continue? If it can't work around the exception, then there's no benefit to it having the exception IMO. Having the nice knowledge that your `handle_foo` isn't ever going to throw will make your calling code much easier to read. – TZHX Feb 24 '15 at 09:01
  • 1
    You have to prevent any exception from leaving a function that is called from a C function and try catch is the way to do it. If you need your exception to "travel through" a C-layer, you can use a global exception ptr (not saying this would be good style). Essentially that gives you a more powerfull errno mechanism, but you still have to ccheckmanually for an error when your c-code returns. – MikeMB Feb 24 '15 at 09:14
  • 1
    `Furthermore, I lose some useful features such as the ability to find out what was thrown`. Catch `std::exception` and log `exception::what`. Do not throw anything that is not derived from `std::exception`. – sbabbi Feb 24 '15 at 11:39

2 Answers2

7

Yes, you have to catch the exceptions and hopefully translate them into something useful. Letting exceptions propagate through C code leads to undefined behavior. At best you cannot expect the C code to maintain consistent program state.

See this answer for an easy example. A harder example is with some complex piece of software such as SQLite - C code will grab some mutex and will not release it because the exception simply "flies through" and your program is now toast.

Also this has any chance of "working" if all the code is built against the same C++ runtime. If you happen to have callback implemented in say Visual C++ 9 and the rest of the code in say Visual C++ 10 or those parts are compiled against static runtime libraries - you now have two distinct runtimes and the unhandled exception in the callback causes terminate() being called.

Community
  • 1
  • 1
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • Thanks for confirming my suspicions. So, not only is it a good idea to catch all exceptions, it's actually required. What about determining the nature of the exception? Is there no general method beyond breaking out the catches into separate cases? – nneonneo Feb 24 '15 at 09:21
  • @nneonneo You could log or othwerwise show the exception details in the callback. Also you could stash the details aside in some kind of global variable - that's what COM `SetErrorInfo()` does. – sharptooth Feb 24 '15 at 09:26
  • 1
    @nneonneo: good libraries will either derive their exceptions (directly or indirectly) from `std::exception`, or have one or at worst a few base classes in their own exception hierarchy... in general you can just have explicit catch cases for those base classes, then use their virtual functions to get details of the errors. If that gets really verbose and has to be reproduced in hundreds of places in your code, consider adding an extra layer of functions that maps back to a reasonable number of exceptions, or - last resort - falling back on a macro :-(. – Tony Delroy Feb 24 '15 at 09:40
3

sharptooth answered most of your question, and James McNellis has written a nice blog post about how to elegantly get rid of the boilerplate using modern C++.

The gist of it is having a translate_exceptions function used like this:

int callback(...) {
   return translate_exceptions([&]{
      // ... your code here
   });
}

The translate_exception is a simple function template (which uses a function level try block) taking an invokable:

template<typename Fn> int translate_exception(Fn fn) try {
  fn();
  return 0;
} catch(std::exception const& e) {
  LOG("[EE] exception ", e.what());
  return -2;
} catch( ... ) {
  LOG("[EE] unknown exception");
  return -1;
}
Fabio Fracassi
  • 3,791
  • 1
  • 18
  • 17