Forgive me on the long post. Its a complicate problem I wanted a complete description.
On Linux Fedora 21 (g++ 4.9.2) and like.
I’m working on a C++ database wrapper library using a base class of “database” and inherited classes of Oracle and Sybase.
Beyond that, I wanted some of the programs to be able to dynamically load the libraries at runtime with dlopen, following the template from http://www.linuxjournal.com/article/3687?page=0,0 and others.
Other programs however to directly link the version they need. The problem with my solution is there exists a static std::map in database.cpp (see code below) that must be initialized before it is assigned in the static initializer of oracle / sybase.cpp. The only way I’ve been able to accomplish this is by the physical order of the -l arguments at compile time (see Makefile). Get them correct:, program runs fine. Get them backwards: — and program compiles & links find, but will crash immediately on execution. This troubles me as prefer a successful compile/link to yield a successful run and a seemly flip of library order usually doesn’t do this. I know not uncommon for link order to cause link errors, but not runtime errors.
Two questions:
- Is there some other link option I can use to ensure proper initialization besides library order?
- Can anyone see another algorithm that would eliminate this dependancy. (I don’t want the normal programs to have to declare DBFactory, it needs to stay in the libraries, but an extra step in main_dlopen would be fine).
Here is the code, all example modules and Makefile. 2 main programs are included, one for normal linking (main_direct) and one for runtime linking (main_dlopen). Both will compile and run as given (unless there is a cut-n-paste typo).
Thanks for your attention…
// database.h #include <map> #include <string> #include <iostream> #include <stdio.h> class Database; // forward declaration typedef Database* maker_t(); // our global factory typedef std::map<std::string, maker_t *> DBFactory_t; extern DBFactory_t DBFactory; class Database { public: Database () {} virtual ~Database () {}; };
// database.cpp #include "database.h" __attribute__((constructor)) void fooDatabase (void) { fprintf (stderr, "Database library loadedn"); } // our global factory for making dynamic loading possible // this is the problem child static global std::map<std::string, maker_t * > DBFactory ;
// oracle.h #include "database.h" class Oracle : public Database { public: Oracle (); virtual ~Oracle (); };
// oracle.cpp class. #include "oracle.h" using namespace std; __attribute__((constructor)) void fooOracle (void) { fprintf (stderr, "Oracle library loadedn"); } // the following code follows the referece at // http://www.linuxjournal.com/article/3687?page=0,0 extern "C" { Database * Maker() { return new Oracle; } class proxy { public: proxy () { // register the maker with the factory fprintf (stderr, "Oracle Proxy Constructorn"); DBFactory["ORACLE"] = Maker; } }; } proxy p; Oracle::Oracle () { cout << "Oracle Constructor" << endl; } Oracle::~Oracle () { cout << "Oracle Destructor" << endl; }
// sybase.h #include "database.h" class Sybase : public Database { public: Sybase (); virtual ~Sybase(); };
// sybase.cpp class. #include "sybase.h" using namespace std; __attribute__((constructor)) void fooSybase (void) { fprintf (stderr, "Sybase library loadedn"); } extern "C" { Database * Maker() { return new Sybase; } class proxy { public: proxy () { // register the maker with the factory fprintf (stderr, "Sybase Proxy Constructorn"); DBFactory["SYBASE"] = Maker; } }; } proxy p; Sybase::Sybase () { cout << "Sybase Constructor" << endl; } Sybase::~Sybase () { cout << "Sybase Destructor" << endl; }
// main_direct.cpp #include "oracle.h" int main () { Oracle db (); return 0; }
// main_dlopen.cpp #include <iostream> #include <dlfcn.h> #include <stdlib.h> #include "database.h" using namespace std; int main () { void * dl = dlopen ("libSybase.so", RTLD_NOW); if (!dl) { cerr << dlerror() << endl; exit (1); } Database * db = DBFactory["SYBASE"] (); delete db; return 0; }
#Makefile CXXFLAGS = -Wall -fPIC -ggdb -std=c++11 all: main libOracle.so libSybase.so libdb.so main: main_dlopen main_direct main_dlopen: main_dlopen.o libdb.so libOracle.so libSybase.so ${CXX} -o main_dlopen main_dlopen.o -L. -ldb -ldl # reverse -lOracle and -ldb to make this program crash main_direct: main_direct.o libdb.so libOracle.so libSybase.so ${CXX} -o main_direct main_direct.o -L. -lOracle -ldb libOracle.so: oracle.o ${CXX} -shared -fPIC -o libOracle.so oracle.o libSybase.so: sybase.o ${CXX} -shared -fPIC -o libSybase.so sybase.o libdb.so: database.o ${CXX} -shared -fPIC -o libdb.so database.o clean: rm -f *.o *.so main_dlopen main_direct %.o : %.cpp ${CXX} ${CXXFLAGS} -c $< -o $@
Advertisement
Answer
This is a fairly standard problem: you have global data, with no way to control when it is initialized.
There is also a standard solution to that problem: indirect this data intialization through a function call.
Instead of having global std::map<...> DBFactory
, do this:
// database.cpp DBFactory_t& getDBFactory() { static DBFactory_t factory; return factory; } // oracle.cpp proxy () { // register the maker with the factory fprintf (stderr, "Oracle Proxy Constructorn"); getDBFactory()["ORACLE"] = Maker; }
Voila: the factory
will be constructed the first time you need it.
You have an additional problem, which you haven’t identified yet: both oracle.cpp
and sybase.cpp
define function Maker
, class proxy
and variable p
in global namespace, leading to ODR violation and undefined behavior if both files are loaded into a single process. You better use separate namespaces to avoid that.