I'm having some trouble with exceptions not functioning correctly (or at least, as I would hope; I know there are issues with this) across shared libraries when loaded using dlopen. I include some simplified example code here. The actual situation is myapp=Matlab, myext1=mexglx matlab extension, mylib is a shared library of my code between the two extensions (myext1, myext2)
mylib.h
struct Foo { Foo(int a); m_a; }
void throwFoo();
mylib.cpp
#include "mylib.h"
Foo::Foo(int a): m_a(a) {}
void throwFoo() { throw Foo(123); }
myext1.cpp
#include "mylib.h"
#include <iostream>
extern "C" void entrypoint()
{
try { throwFoo(); }
catch (Foo &e) { std::cout << "Caught foo\n"; }
}
myext2.cpp Identical to myext1.cpp
myapp.cpp
#include <dlfcn.h>
int main()
{
void *fh1 = dlopen("./myext1.so",RTLD_LAZY);
void *fh2 = dlopen("./myext2.so",RTLD_LAZY);
void *f1 = dlsym(fh1,"entrypoint");
void *f2 = dlsym(fh2,"entrypoint");
((void (*)())func1)(); // call myext1 (A)
((void (*)())func2)(); // call myext2 (B)
}
Compiling this code:
g++ mylib.cpp -fPIC -o libmylib.so -shared
g++ myext1.cpp -fPIC -o myext1.so -shared -L. -lmylib -Wl,-rpath=.
g++ myext2.cpp -fPIC -o myext2.so -shared -L. -lmylib -Wl,-rpath=.
g++ myapp.cpp -fPIC -o myapp -ldl
The call to entrypoint() at A works as expected, with throwFoo() throwing the exception and entrypoint() catching it. The call at B however fails to catch the exception. Adding more diagnostic code shows that the typeinfo for the Foo class differs in the two extensions. Changing the order of the two dlopen calls makes no difference, the second loaded extension fails.
I know I can fix this by using RTLD_GLOBAL as an additional flag for dlopen, but the application (Matlab) using dlopen is out of my control. Is there anything I can do with mylib or myext1, myext2 to fix this problem?
I have to avoid using LD flags for runtime (since I cannot control the users running the Matlab binary). Any other suggestions?
Rule 62 in "C++ Coding Standards" by Alexandrescu & Sutter:
"62. Don’t allow exceptions to propagate across module boundaries."
Although it can work when you do it carefully, for true portable and reusable code, this cannot be done. I would say it is a pretty common general rule when programming shared libraries or DLLs, do not propagate exceptions across module boundaries. Just use a C-style interface, return error codes, and do everything inside an exported function inside a try { } catch(...) { };
block. Also, the RTTI is not shared across modules, so don't expect Foo to have the same typeinfo in different modules.
A simple work-around is to have your library dlopen
itself with the RTLD_GLOBAL
flag on the first use. This will override the previous open with RTLD_LOCAL
and put everything in the global symbol namespace.
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