views:

429

answers:

5

I have a C++ application that calls SQLite's (SQLite is in C) sqlite3_exec() which in turn can call my callback function implemented in C++. SQLite is compiled into a static library.

If an exception escapes my callback will it propagate safely through the C code of SQLite to the C++ code calling sqlite3_exec()?

+19  A: 

My guess is that this is compiler dependent. However, throwing an exception in the callback would be a very bad idea. Either it will flat-out not work, or the C code in the SQLite library will be unable to handle it. Consider if this is some code in SQLite:

{
  char * p = malloc( 1000 );
  ...
  call_the_callback();  // might throw an exception
  ...
  free( p );
}

If the exception "works", the C code has no possible way of catching it, and p will never be freed. The same goes for any other resources the library may have allocated, of course.

anon
For the same reason, I wouldn't even throw exceptions from within callback code called by a C++ library, unless its documentation clearly permits this behaviour. There's much (bad) code out there that is written without exceptions in mind.
Alexander Gessler
Then it's back to a global variable to store the exact error cause :)
Matthieu M.
@Matthieu well, it won't have to be global - class static, perhaps?
anon
@Neil: yes probably, but then a class static is akin to a singleton which is itself akin to a global variable. I am always wary of shared state! I don't think one can do much better in this case though.
Matthieu M.
+7  A: 

SQLite is expecting you to return a SQLITE_ABORT on error and a 0 return code for no error. So you ought to wrap all your C++ callback in a try catch. Then in the catch return a SQLite SQLITE_ABORT error code, otherwise a zero.

Problems will occur if you bypass returning through SQLite as it will not free up/complete whatever code it does after you return back from your callback. This will cause untold problems potentially some of which maybe very obscure.

Tony Lambert
+1  A: 

If your callback called from sqlite is from the same thread from which you called sqlite3_exec() a throw somewhere in the callstack should be caught by a higher level catch.

Testing this yourself should be straightforward, no?

[edit] After digging a little more myself I found out that the C++ standard is somewhat vague on what the behavior a c++ function called from c should have when throwing an exception.

You should definitely use the error handling mechanism the API expects. Otherwise you'll mostly the API itself in an undefined state and any further calls could potentially fail/crash.

Francis Boivin
First of all, it may be implementation-dependent or undefined behavior. Also I completely forgot about exception-safety of C code when asked this question. So just writing a "Hello world" and running it is not enough.
sharptooth
+9  A: 

There is already a protocol for the callback to abort the API call. From the docs:

If an sqlite3_exec() callback returns non-zero, the sqlite3_exec() routine returns SQLITE_ABORT without invoking the callback again and without running any subsequent SQL statements.

I'd strongly recommend you use this instead of an exception.

Hans Passant
He CAN use exceptions within his C++ code. Just make sure the C++ code in the callback is wrapped in a try catch so they don't rollback any further than the wrapper function, eg... not into SQLite or beyond. See my answer.
Tony Lambert
Well, obviously. Worth a downvote?
Hans Passant
+1  A: 

That was a really interesting question and I tested it out myself out of curiosity. On my OS X w/ gcc 4.2.1 the answer was YES. It works perfectly. I think a real test would be using gcc for the C++ and some other (MSVC?, LLVM?) for the C part and see if it still works.

My code:

callb.h:

#ifdef __cplusplus
extern "C" {
#endif

typedef void (*t_callb)();
void cfun(t_callb fn);

#ifdef __cplusplus
}
#endif

callb.c:

#include "callb.h"

void cfun(t_callb fn) {
 fn();
}

main.cpp:

#include <iostream>
#include <string>
#include "callb.h"

void myfn() {
  std::string s( "My Callb Except" );
  throw s;
}

int main() {
  try {
    cfun(myfn); 
  }
  catch(std::string s) {
    std::cout << "Caught: " << s << std::endl;
  }
  return 0;
}
Gianni