Reputation: 25
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.
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 loaded\n");
}
// 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 loaded\n");
}
// 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 Constructor\n");
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 loaded\n");
}
extern "C" {
Database * Maker()
{
return new Sybase;
}
class proxy {
public:
proxy ()
{
// register the maker with the factory
fprintf (stderr, "Sybase Proxy Constructor\n");
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 $@
Upvotes: 1
Views: 889
Reputation: 213646
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 Constructor\n");
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.
Upvotes: 3