Once upon a time there was a bright young C++ programmer… Well, not so young, actually. And, sadly, not bright enough to know how to use C++ member functions in a Linux dynamic library. I'll tell you his poignant story in the hope that you can avoid his mistakes.
I will call this programmer "Phil" (not his real name, apparently). Now, Phil had read the manual and he knew how to build a shared library using gcc. Just pass the -fPIC flag to the compiler (to generate position-independent code) and pass the -shared flag to the linker (to generate a shared library). He also knew how to load the library into his main program using dlload(), get the address of a function or data item in the library using dlsym(), check for errors using dlerror() and close the library using dlclose(). Now, dlsym() takes the name of a symbol and returns its address. But what is the name of a C++ member function? Typically, it is a mangled version of something like "Isotek::SignalMonitor* Isotek::SignalMonitor::create()". Not only is this quite a lot of typing, it is also tricky to find the mangled name and it will change with the slightest change to the function's name or interface.
"No sweat," thought Phil, for he had solved that problem when creating plug-in libraries for Win32 operating systems. The trick is to have the plug-in library insert its objects into a suitable registry when it loads. The main program creates the registry, loads the plug-in library and uses the objects that magically appear in the registry. No symbol look-ups required. Perfect.
Well, not quite. There was a two-way dependency between the main program and the plug-in library which, in Win32, meant that linking was a real pain. But Phil had another trick up his sleeve. Move the registry into its own shared library. Now both the main program and the plug-in library can be linked with the registry library in the normal way. The operating system automatically loads the registry library when the main program is started and it's already there when the plug-in library loads.
So, Phil built the registry library, the plug-in library and the main program, ran a test and saw that it was good. The main program found the object created by the plug-in library and correctly called its member functions. The problem of calling C++ functions with mangled names in Linux plug-in libraries was solved.
Then Phil tested an error condition (as every good programmer does). A function in the plug-in library detected the error and threw an exception; the exception handler in the main program failed to catch the exception and aborted. Phil checked his code carefully, but there was no obvious coding error. He poked around in the debugger, he tried to think of explanations for this behaviour, he discussed it with a colleague, but to no avail. So, finally, as an experiment, he tried to catch the exception within the function that threw it. The program still crashed. Exception handlers were not invoked for exceptions thrown from a function in the plug-in library.
This was serious. Exceptions are often a good way of handling error conditions. The C++ standard library throws exceptions. Even the core language throws exceptions (bad_alloc and bad_cast). Did this mean that we could only use a subset of C++ in dynamic libraries? After more discussions and a search on the Web Phil discovered that the problem had been mentioned in a newsgroup post. The newsgroup thread contained just two messages. The first provided code that demonstrates the problem; the second said "but, it works for me". The difference had to be the compiler/linker switches. Sure enough, in a stripped-down sample program, exceptions were not caught when the -nostartfiles switch was provided, but were caught when this switch was absent.
Unfortunately, Phil needed the -nostartfiles switch. His strategy relies on the plug-in registering its objects when it loads. To do this, the programmer provides a function with C linkage called _init() and the operating system calls this function when the library loads. The programmer may also provide a _fini() function called when the library is unloaded. But, the C/C++ start-up files contain _init() and _fini() functions, too. The -nostartfiles switch prevents "multiple definition" errors from the linker by suppressing the inclusion of the standard start-up files in the executable file.
The moral of this story? Don't supply your own _init() or _fini() function in Linux dynamic libraries containing C++ functions that may throw exceptions. That rules out C++ functions that use new, std::vector, std::string (to name but three), either directly or indirectly. And that doesn't leave very much.
Phil fixed his problem by removing the leading underscore from the _init() and _fini() functions in the library. The renamed functions must now be called explicitly using dlsym() to look up their addresses. Note, however, that only these two functions need to be looked up by name and they are both simple functions with C linkage and unmangled names.
 I confess I don't know what the _init() and _fini() functions in the start-up files do. Presumably they perform some initialisation of the C/C++ library and this includes initialisation of the exception handling mechanism.
 Thaddeus Frogley suggested a simple class that loads a dynamic library and makes the init()/fini() calls via dlsym(). Good idea. Thanks, Thad.
Overload Journal #48 - Apr 2002 + Programming Topics
|Browse in :||
All > Topics > Programming (768)
Any of these categories - All of these categories