Reputation: 115
I am building a shared library that I want to be ABI compatible between different compilers (like MSVC and GCC on Windows). I took my inspiration from this blog post. The only thing that I missed was the ability to throw exceptions across the DLL boundaries... So I made this little trick :
MyLibrary.hpp
class Thrower{
static void(*m_thowFunc)(const char*);
public:
static void setThrowFunction(void(*func)(const char*)){
m_thowFunc = func;
}
static void throwException(const char* msg){
m_thowFunc(msg);
}
};
extern "C" {
EXPORT void MyLibrary_setThrowFunction(void(*func)(const char*));
EXPORT void MyLibrary_foo();
}
MyLibrary.cpp
extern "C" {
EXPORT void MyLibrary_setThrowFunction(void(*func)(const char*)){
Thrower::setThrowFunction(func);
}
EXPORT void MyLibrary_foo(){
Thrower::throwException("oops, an error occured...");
}
}
and in the client code
void defaultThrowFunction(const char* msg){
throw std::runtime_error(msg);
}
int main(){
MyLibrary_setThrowFunction(&defaultThrowFunction);
try{
MyLibrary_foo();
}catch(std::runtime_error& e){
//handle exception
}
}
This works like a charm. I can handle exceptions thrown from the DLL (in fact, thrown by the client code) in the client code. The only downside that I am aware of is that I have tons of warnings while compiling the DLL since "not all control path return a value"...
Am I missing something here ? Is this really a good idea or not at all ?
Upvotes: 3
Views: 3222
Reputation: 275976
This may work, but only if the throwing mechanism and stack unrolling code of the two code bases is basically identical and compatible.
In order to destroy each of the objects it should after that throw, the client code has to be able to understand how the DLL code sets up its stack and where it registers destructors to be cleaned up, etc. It may be the case that your client and DLL code compilers are sufficiently in agreement for this to work, if you are lucky.
Which sort of ruins the point of your design.
An approach that might work is to carefully marshal the exceptions over the DLL boundary.
The "C" API for your DLL returns information about what exceptions (if any) where thrown. A C++ header-file only wrapper that the client compiles calls the "C" API, and in client-compiled code detects the exception, unwraps it, and throws it.
Inside the DLL, the "C" API does a try{}catch(){}
, and calls the internal C++ library. The catch
clause populates the exception information into the "C" API return value, and returns.
Now internal exceptions are thrown, caught within the C++ DLL, marshaled over the DLL-boundary in a "C" API, packaged back into exceptions on the client-code side, and rethrown.
Upvotes: 2
Reputation: 30021
Throwing exceptions across DLL boundaries is a bad idea in general unless everything is under your complete control.
By everything, I mean: the thrower and the catcher should use the same compiler version, the same standard library, and the same runtime instance. All of these things can throw off exception handling -- hopefully with just a crash, but maybe with a more unfortunately subtle result.
Upvotes: 1