这几天一直在弄C++、C的动态链接库的时候,经过了近4天的努力研究和整合,终于把这个功能应用到了CEP项目中,特此笔记。
有关动态链接库的概念,何时使用,使用的优点就不在此多多解释了,下面,简单记录一下使用的具体过程。
有关动态链接库的概念,何时使用,使用的优点就不在此多多解释了,下面,简单记录一下使用的具体过程。
1、涉及到的库,该库中提供了四个轻松调用动态链接库的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
#include
using namespace std;
该实例的场景是:把student.cpp和teacher.cpp写成动态库的形式提供给callPlugin.cpp调用,如果再需要开发一个其他的类如:police.cpp,只需要按照有关约定编写好,再编译成动态库,则可不要更改任何应用程序的框架代码,即 实现了插件式开发。
1)base.h,该文件的作用是定义两个基本方法,也就是接口方法,提供给接口调用者和接口实现者使用。
// base.h
#include
#include
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
#include
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
#include
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、以上是我最近自己所学的小总结,希望能给正在研究这方面知识的朋友有些许帮助,我也是刚学,所有,如果有什么错误,还望不吝指出,谢谢!