這幾天一直在弄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)
返回在調用上述方法失敗時的具體錯誤信息。
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;
該實例的場景是:把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";
// 下面兩個方法必須都用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"
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);
}
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"
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;
}
}
};
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);
}
//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);
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、以上是我最近自己所學的小總結,希望能給正在研究這方面知識的朋友有些許幫助,我也是剛學,所有,如果有什麼錯誤,還望不吝指出,謝謝!
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、以上是我最近自己所學的小總結,希望能給正在研究這方面知識的朋友有些許幫助,我也是剛學,所有,如果有什麼錯誤,還望不吝指出,謝謝!