Linux C ++动态库和静态初始化顺序

时间:2015-05-31 21:35:49

标签: c++ linux dynamic initialization shared-libraries

在长篇文章中原谅我。这是一个复杂的问题,我想要一个完整的描述。

在Linux Fedora 21(g ++ 4.9.2)等上。

我正在使用基类“数据库”和继承的Oracle和Sybase类来开发C ++数据库包装器库。

除此之外,我希望一些程序能够在运行时使用dlopen动态加载库,遵循http://www.linuxjournal.com/article/3687?page=0,0和其他人的模板。

然而,其他程序直接链接他们需要的版本。我的解决方案的问题是在database.cpp中存在一个静态std :: map(参见下面的代码),必须在oracle / sybase.cpp的静态初始化程序中分配之前对其进行初始化。我能够完成此任务的唯一方法是在编译时使用-l参数的物理顺序(请参阅Makefile)。 让它们更正:,程序运行正常。 让他们倒退 - 并编制程序编译&链接查找,但会在执行时立即崩溃。这让我感到困扰,因为我更喜欢成功的编译/链接以获得成功的运行,并且图书馆顺序的轻微翻转通常不会这样做。我知道链接顺序导致链接错误并不罕见,但不是运行时错误。

两个问题:

  1. 我是否可以使用其他链接选项来确保除库顺序之外的正确初始化?
  2. 任何人都可以看到另一种可以消除这种依赖性的算法。 (我不希望普通程序必须声明DBFactory,它需要保留在库中,但是main_dlopen中的额外步骤会没问题。)
  3. 这是代码,所有示例模块和Makefile。包括2个主程序,一个用于普通链接(main_direct),另一个用于运行时链接(main_dlopen)。两者都将按照给定的方式编译和运行(除非有一个cut-n-paste拼写错误)。

    感谢您的关注......

    //  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 $@
    

1 个答案:

答案 0 :(得分:2)

这是一个相当标准的问题:你有全局数据,无法控制何时初始化。

该问题还有一个标准的解决方案:通过函数调用间接地进行数据初始化。

不要使用全局std::map<...> DBFactory,而是执行此操作:

// 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:factory将在您第一次需要时构建。

您还有一个问题尚未确定:oracle.cppsybase.cpp定义了函数Makerclass proxy和变量{{1}在全局命名空间中,如果两个文件都加载到单个进程中,则会导致ODR违规和未定义的行为。您最好使用单独的命名空间来避免这种情况。