linux C++/C 動態鏈接庫使用

 
這幾天一直在弄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、以上是我最近自己所學的小總結,希望能給正在研究這方面知識的朋友有些許幫助,我也是剛學,所有,如果有什麼錯誤,還望不吝指出,謝謝!
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章