这几天一直在弄C++、C的动态链接库的时候,经过了近4天的努力研究和整合,终于把这个功能应用到了CEP项目中,特此笔记。
有关动态链接库的概念,何时使用,使用的优点就不在此多多解释了,下面,简单记录一下使用的具体过程。
1、涉及到的库<dlfcn.h>,该库中提供了四个轻松调用动态链接库的API
 a) void *dlopen (const char *so_file_path, int open_mode)
  dlopen是打开动态链接库文件的API,这里so_file_path是so文件的路径,open_mode是打开so文件的模式,
  常用的有两种:RTLD_NOW和RTLD_LAZY,
  RTLD_NOW:在dlopen()方法调用完成之前就去动态的解析so文件里面的所有未定义的符号,如果无法解析,则打开失败。
  RTLD_LAZY: 只有当so文件里面的未定义的符号在真正使用的时候才去解析。
  这里要注意的是:如果加载的动态库还依赖其他的动态库,必须使用RTLD_NOW。
  函数调用成功,则返回该so文件的句柄(指针)so_handle,否则返回NULL。
 b) void *dlsym (void *so_handle,const char *method_name)
  dlsym的调用是用来得到so文件某个具体的函数的指针的,函数调用成功则返回method_name函数的指针,否则返回NULL。
  so_handle:使用dlopen()返回的so句柄
  method_name:定义在so中的方法名(这里要注意的是,该so方法中不能有重载的函数,当然,c语言是不支持函数重载的)
 c) int dlclose (void *so_handle)
  关闭dlopen()返回的so句柄
  这里要注意:如果在使用dlsym()返回的函数指针的时候调用了该方法,那么,肯定会出现Segment fault的错误,因为调用此方法之后,代表对so动态库的资源回收。
 d) char *dlerror (void)
  返回在调用上述方法失败时的具体错误信息。
2、下面来看一个简单的实例:
 该实例的场景是:把student.cpp和teacher.cpp写成动态库的形式提供给callPlugin.cpp调用,如果再需要开发一个其他的类如:police.cpp,只需要按照有关约定编写好,再编译成动态库,则可不要更改任何应用程序的框架代码,即 实现了插件式开发。
 1)base.h,该文件的作用是定义两个基本方法,也就是接口方法,提供给接口调用者和接口实现者使用。
 // base.h
 #include<iostream>
 #include<string>
 using namespace std; 
 class BasePeople;
 // 下面两个方法必须都用extern "C"进行修饰,这个主要是因为C++中的符号命名方法和C的不一样,具体可以查看有关资料
 // 在C++中,方法method(int a, float b)可能会被命名为:method_int_float,而在C++中则命名为:method
 // 这其实就是C++能支持函数重载,而C却不能的主要原因之一
 extern "C" BasePeople* create_BasePeople(const string & name, const int age);
 extern "C" void delete_BasePeople(BasePeople * pp);
 
 static const string __CREATE_OBJECT__ = "create_BasePeople";
 static const string __DELETE_OBJECT__ = "delete_BasePeople";
 class BasePeople{
  public:
   BasePeople(const string & name, const int age) : name_(name),age_(age){
    cout << "BasePeople's constructor invoked" << endl;
   }
   
   virtual ~BasePeople(){
    cout << "BasePeople's de-constructor invoked" << endl;
   }
   
   virtual void speak() = 0;
   void test(){
    cout << "This is the test method!" << endl;
   }
  protected:
   int age_;
   string name_;
 };
 
 2)student.cpp,该文件是class BasePeople的继承类,同时,该文件将被生成libstudent.so动态库,同时提供给callPlugin.cpp调用
 // student.cpp
 #include "base.h" 
 class student : public BasePeople{
  public:
   student(const string & name, const int age) : BasePeople(name,age)
   {
    cout << "student's constructor invoked" << endl;
   }
   
   virtual ~student(){
    cout << "student's de-constructor invoked" << endl;
   }
   
   virtual void speak(){
    int i = 0;
    while(i < 10){
     cout << "I am a student, my name is " << this->name_ << ", and my age is " << this->age_ << endl;
     sleep(1);
     ++ i;
    }
   }
 };
 
 // 动态库文件libstudent.so中必须包含有在base.h中申明的两个extern "C" 方法
 BasePeople* create_BasePeople(const string & name, const int age){
  //student* sd = new student(name,age);
  return new student(name,age);
 }
 void delete_BasePeople(BasePeople * pp){
  if(pp)
   delete pp;
 }
 
 3)teacher.cpp,该文件和student.cpp一样,只是会生成libteacher.so动态库。
 // teacher.cpp
 #include "base.h"
 class teacher : public BasePeople{
  public:
   teacher(const string & name, const int age) : BasePeople(name,age)
   {
    cout << "teacher's constructor invoked" << endl;
   }
   
    virtual ~teacher(){
    cout << "teacher's de-constructor invoked" << endl;
   }
   
   virtual void speak(){
    int i = 0;
    while(i < 10){
     cout << "I am a teacher, my name is " << this->name_ << ", and my age is " << this->age_ << endl;
     sleep(1);
     ++ i;
    }
   }
 }; 
 BasePeople* create_BasePeople(const string & name, const int age){
  //teacher* sd = new teacher(name,age);
  return new teacher(name,age);
 }
 void delete_BasePeople(BasePeople * pp){
  if(pp)
   delete pp;
 }
 
 4)callPlugin.cpp调用类的实现
 // callPlugin.cpp
 #include "base.h"
 #include <dlfcn.h>
 #include <pthread.h>
 
 class callPlugin{
  typedef BasePeople* ObjectPtr;
  typedef ObjectPtr (*createObjectPtr)(const string &, const int);
  typedef void (*deleteObjectPtr)(ObjectPtr);
  public:
   callPlugin(const string & soPath, const int openMode = RTLD_LAZY) : soPath_(soPath), openMode_(openMode){
    cout << "callPlugin constructor invoked!" << endl;
   }
   
   ~callPlugin(){
    cout << "callPlugin de-constructor invoked!" << endl;
   }
   
   int excute(){
    void * pluginHandler = openPlugin();
    if(pluginHandler){
     // 得到create_BasePeople方法的指针
     createObjectPtr createFunc = (createObjectPtr) dlsym(pluginHandler,__CREATE_OBJECT__.c_str());
     ObjectPtr people = NULL;
     if(createFunc){
      people = createFunc("luoxiaoyi",23);
      people->speak();
      people->test();
     }else{
      cout << "callPlugin::excute() createFunc error :" << dlerror() << endl;
     }
     
     // 得到delete_BasePeople方法的指针
     deleteObjectPtr deleteFunc = (deleteObjectPtr) dlsym(pluginHandler,__DELETE_OBJECT__.c_str());
     
     if(deleteFunc){
      deleteFunc(people);
     }else{
      cout << "callPlugin::excute() deleteFunc error :" << dlerror() << endl;
     }
     
     // 关闭pluginHandler,回收资源
     dlclose(pluginHandler);
     pluginHandler = NULL;
    }else{
     cout << "callPlugin::excute() pluginHandler error :" << dlerror() << endl;
    }
   }
  private:
   // so动态库的路径,可以是绝对路径,也可以是相对路径
   string soPath_;
   // so动态库的打开模式RTLD_NOW,RTLD_LAZY
   int openMode_;
   
   /**
   * 调用dlopen,同时得到相关so文件的句柄
   */
   void* openPlugin(){
    if(soPath_.size() < 1){
     cout << "plugin file[soPath=" << soPath_ << "] cannot be empty!" << endl;
     return NULL;
    }
    
    if(openMode_ == RTLD_NOW || openMode_ == RTLD_LAZY)
     return dlopen(soPath_.c_str(),openMode_);
    else
     return dlopen(soPath_.c_str(),RTLD_LAZY);
   }
 };
 
 // 供多线程调用的方法
 void* callFun(void *arg){
  callPlugin* cp = (callPlugin*) arg;
  cp->excute();
 }
 /*************************************************** main() **********************************************/
 int main(){
  // 加载动态库
  callPlugin cp1("libstudent.so");        // ----------------------------->标记1
  callPlugin cp2("libteacher.so");   // ----------------------------->标记2
  
  // 创建新线程,用于调用libteacher.so的执行
  pthread_t thread;
  pthread_create(&thread,NULL,callFun,&cp2);
  
  // 执行
  cp1.excute(); 
  
  // 等待thread线程执行完毕
  pthread_join(thread,NULL);
  return 0;
 }
 
 5)编译指令
 g++ teacher.cpp -fPIC -shared -o /usr/local/cep/libteacher.so
 g++ student.cpp -fPIC -shared -o /usr/local/cep/libstudent.so
 g++ callPlugin.cpp base.cpp -o callPlugin -ldl -pthread
 
 6)可能出现的问题以及参考解决方案
 编译完成之后,如果你此时运行./callPlugin,则可能会报无法找到动态库的错误。解决的方法如下:
 a)讲main方法中的标记1和2里面so文件的路径改为绝对路径/usr/local/cep/libstudent.so、/usr/local/cep/libteacher.so,重新编译callPlugin.cpp,然后运行,如果还不行尝试b);
 b)如果你有root权限,可以通过修改/etc/ld.so.conf来达到此目的,修改方法为:在/etc/ld.so.conf中加入你的so所在的目录,在这里如:/usr/local/cep,完了之后,再运行ldconfig命令,至于为何要这样,您可以网上搜下/etc/ld.so.conf的作用,如果没有root权限,则可尝试c);
 c)没有root权限的情况,可以通过用环境变量LD_LIBRARY_PATH指定路径,但是这里要注意的是,不同的系统可能环境变量不一样,指定方法如:LD_LIBRARY_PATH=.;这样就把LD_LIBRARY_PATH指定为当前目录,然后使用export LD_LIBRARY_PATH命令即可。
 
 通过以上三步,你基本可以解决库文件无法找到的问题了,如果还是无法解决相关问题,那么请网上找找对应的错误咯
 注意:添加了-c参数,即g++ -c那么很有可能出现only ET_DYN and ET_EXEC can be loaded错误,解决方法简单,去掉-c即可。
 
3、以上是我最近自己所学的小总结,希望能给正在研究这方面知识的朋友有些许帮助,我也是刚学,所有,如果有什么错误,还望不吝指出,谢谢!