文章目录
声明:内容出自老师PPT,非我原创 |
插件:一个已经编译运行的程序,还可以动态增加内容
例如:Microsoft Visual Studio 统计行数、高亮括号的插件
动态库的编写与使用
代码
a.h
void f();
extern "C" void g();
a1.cpp
#include<iostream>
#include"a.h"
using namespace std;
void f()
{
cout << "f()" << endl;
}
a2.cpp
#include<iostream>
#include"a.h"
using namespace std;
extern "C" void g()
{
cout << "g()" << endl;
}
test.cpp
#include"a.h"
#include<dlfcn.h>
#include<iostream>
using namespace std;
int main()
{
void *handle = dlopen("./libtest.so", RTLD_LAZY);
if(0 == handle)
{
cout << "dlopen error" << endl;
return 0;
}
typedef void (*Fun)();
Fun f1 = (Fun)dlsym(handle, "f");
if(0 == f1)
{
cout << "f1 error" << endl;
char *str = dlerror();
cout << str << endl;
}
(*f1)();
dlclose(handle);
return 0;
}
动态库编写
g++ -fpic -shared -o libtest.so a1.cpp a2.cpp
生成libtest.so供test.cpp使用
动态库使用
示例:test.cpp
打开动态链接库 dlopen
#include<dlfcn.h>
void *dlopen(const char *file, int mode);
参数
file:动态链接库的文件名,包括路径信息
mode:动态链接库的使用方式,例如RTLD_LAZY:动态的加入动态链接库中的函数
返回值:引用动态链接库的句柄;出错返回NULL
映射动态链接库中的函数 dlsym
#include<dlfcn.h>
void *dlsym(void *handle, const char *FuncName);
参数
handle:dlopen的返回值
FuncName:动态链接库中的函数名
返回值:FuncName函数被加载后,在进程地址空间中的地址;出错返回NULL
查看出错原因 dlerror
#include<dlfcn.h>
char *dlerror();
返回值
当dlopen、dlsym等函数出错时,dlerror返回字符串说明这些函数出错的原因
卸载动态链接库 dlclose
#include<dlfcn.h>
int dlclose(void *handle);
参数
handle:dlopen的返回值
编译
g++ -o test test.cpp -ldl
test
这里会发现欸出错了
函数导出名命名规则
查看动态库导出的函数
nm libtest.so
f函数实际上在动态库中的名字是:_Z1fv
基本支持函数重载的语言都需要进行函数导出名命名。
目的就是为了给重载的函数不同的签名,以避免调用时的二义性调用。
使用extern "C"
使得导出函数名称和实际名称一致
extern “C”:告诉编辑器按C语言的方式设定函数的导出名
不同的编译器、不同的语言,对函数名的修改都有可能不同
如a2.cpp
就是正确用法
把函数前面加上extern "C"
Makefile
test:test.o libtest.so
g++ -o test test.o -ldl
rm *.o
test.o:test.cpp
g++ -o test.o -c test.cpp -ldl
libtest.so:a.h a1.cpp a2.cpp
g++ -fpic -shared -o libtest.so a1.cpp a2.cpp
就正常啦!
实例1:更新插件
版本1要求:
开发一个程序,向屏幕打印“Hello World”;
在不重新编译链接原程序的前提下,将打印的文字改为“Hello China”
思路:使用动态链接库实现打印功能
main.cpp
#include <dlfcn.h>
#include <iostream>
using namespace std;
int main()
{
void *handle = dlopen("./libfunc.so", RTLD_LAZY);
if(handle == 0)
{
cout << "dlopen error" << endl;
return 0;
}
typedef void (*FUNC_PRINT)();
FUNC_PRINT dl_print = (FUNC_PRINT)dlsym(handle, "Print");
if(dl_print == 0)
{
cout << "dlsym error" << endl;
return 0;
}
(dl_print)();
dlclose(handle);
return 0;
}
实例2:多插件的使用
版本2要求:
同时要打印“Hello World”,打印“Hello China”,甚至同时打印未来才会增加的其他打印信息
打印未来的这些信息,也不能重新编译链接原程序
提示:
一种打印功能用一个动态链接库(插件)实现
将这些插件放置在固定的目录中,例如当前目录下的plugin子目录 (目录需要提前建好)
遍历这个目录,获取所有动态链接库
function.cpp
#include <iostream>
using namespace std;
extern "C" void Print()
{
cout << "Hello World!" << endl;
}
funcion1.cpp
#include <iostream>
using namespace std;
extern "C" void Print()
{
cout << "Hello China!" << endl;
}
main.cpp
#include <dlfcn.h>
#include <iostream>
#include "CPluginEnumerator.h"
using namespace std;
int main()
{
vector<string> vstrPluginNames;
CPluginEnumerator enumerator;
if(!enumerator.GetPluginNames(vstrPluginNames))
{
cout << "GetPluginNames error" << endl;
return 0;
}
for(int i = 0; i< vstrPluginNames.size(); i++)
{
void *handle = dlopen(vstrPluginNames[i].c_str(), RTLD_LAZY);
if(handle == 0)
{
cout << "dlopen error" << endl;
return 0;
}
typedef void (*FUNC_PRINT)();
FUNC_PRINT dl_print = (FUNC_PRINT)dlsym(handle, "Print");
if(dl_print == 0)
{
cout << "dlsym error" << endl;
return 0;
}
(dl_print)();
dlclose(handle);
}
return 0;
}
CPluginEnumerator.h
#ifndef CPLUGINENUMERATOR_H
#define CPLUGINENUMERATOR_H
#include <vector>
#include <string>
using namespace std;
class CPluginEnumerator
{
public:
CPluginEnumerator();
virtual ~CPluginEnumerator();
bool GetPluginNames(vector<string>& vstrPluginNames);
};
#endif
CPluginEnumerator.cpp
#include "CPluginEnumerator.h"
#include <dirent.h>
#include <string.h>
CPluginEnumerator::CPluginEnumerator()
{
}
CPluginEnumerator::~CPluginEnumerator()
{
}
bool CPluginEnumerator::GetPluginNames(vector<string>& vstrPluginNames)
{
DIR *dir = opendir("./plugin");
if(dir == 0)
return false;
for(;;)
{
struct dirent *pentry = readdir(dir);
if(pentry == 0)
break;
if(strcmp(pentry->d_name, ".") == 0)
continue;
if(strcmp(pentry->d_name, "..") == 0)
continue;
string str = "./plugin/";
str += pentry->d_name;
vstrPluginNames.push_back(str);
}
closedir(dir);
return true;
}
插件被放在了一个约定好的目录,./plugin
当需要实现新的打印功能时,只需要仿造function.cpp,再构造一个新的插件即可
请注意,每个.so文件都要保存到plugin文件夹下;为了方便可以在创建plugin后,将每个连接语句改为
g++ -fpic -shared -o plugin/libfunc.so function.cpp
读目录的基本操作
打开目录(opendir)
函数原型:
#include<dirent.h>
DIR* opendir(const char* pathname);
返回值和参数
返回值:返回打开目录的索引结构,出错返回NULL
pathname:要打开的目录名
逐一读出目录项(readdir、rewinddir)
readdir
用于读取目录项
函数原型:
struct dirent *readdir(DIR *dp);
参数与返回值
dp:由opendir返回的
返回值:dp对应的目录中包含的一个目录项
dirent结构
struct dirent{
ino_t d_ino; //索引节点号
char d_name[NAME_MAX + 1]; //文件名
................
}
获得目录下所有文件
DIR *dir;
struct dirent *ptr;
dir=opendir("/etc/rc.d");
while((ptr=readdir(dir))!=NULL)
{
printf("d_name: %s\n", ptr->d_name);
}
rewinddir
用来设置目录流目前的读取位置为原来开头的读取位置
函数原型
void rewinddir(DIR *dp);
参数
dp:由opendir返回
关闭目录(closedir)
函数原型:
int closedir(DIR *dp);
参数与返回值
dp:由opendir返回
返回值:成功返回0,出错返回-1
实例3:多插件的选择与使用
版本3要求:版本2是同时调用所有插件的打印功能,现在要求一次只调用一种功能
插件导出一个Help接口,向屏幕输出信息
分析:
既然一次只调用一种功能,那我们首先要知道究竟有哪些打印功能
插件导出一个Help接口,向屏幕输出信息,使用户知道该插件实现了何种打印功能,以及该功能对应的ID
简化起见,通过命令行参数main help调用接口
function.cpp
#include <iostream>
using namespace std;
const int FUNC_ID = 1;
extern "C" void Print()
{
cout << "Hello World!" << endl;
}
extern "C" void Help()
{
cout << "Function ID " << FUNC_ID << " : This function will print hello world." << endl;
}
function1.cpp
#include <iostream>
using namespace std;
const int FUNC_ID = 2;
extern "C" void Print()
{
cout << "Hello China!" << endl;
}
extern "C" void Help()
{
cout << "Function ID " << FUNC_ID << " : This function will print hello china." << endl;
}
CPluginEnumerator.h
#ifndef CPLUGINENUMERATOR_H
#define CPLUGINENUMERATOR_H
#include <vector>
#include <string>
using namespace std;
class CPluginEnumerator
{
public:
CPluginEnumerator();
virtual ~CPluginEnumerator();
bool GetPluginNames(vector<string>& vstrPluginNames);
};
#endif
CPluginEnumerator.cpp
#include "CPluginEnumerator.h"
#include <dirent.h>
#include <string.h>
CPluginEnumerator::CPluginEnumerator()
{
}
CPluginEnumerator::~CPluginEnumerator()
{
}
bool CPluginEnumerator::GetPluginNames(vector<string>& vstrPluginNames)
{
DIR *dir = opendir("./plugin");
if(dir == 0)
return false;
for(;;)
{
struct dirent *pentry = readdir(dir);
if(pentry == 0)
break;
if(strcmp(pentry->d_name, ".") == 0)
continue;
if(strcmp(pentry->d_name, "..") == 0)
continue;
string str = "./plugin/";
str += pentry->d_name;
vstrPluginNames.push_back(str);
}
closedir(dir);
return true;
}
main.cpp
#include <dlfcn.h>
#include <iostream>
#include "CPluginEnumerator.h"
#include <string.h>
using namespace std;
int main(int argc, char **argv)
{
if(argc != 2)
return 0;
if(strcmp(argv[1], "help") == 0)
{
vector<string> vstrPluginNames;
CPluginEnumerator enumerator;
if(!enumerator.GetPluginNames(vstrPluginNames))
{
cout << "GetPluginNames error" << endl;
return 0;
}
for(int i = 0; i< vstrPluginNames.size(); i++)
{
void *handle = dlopen(vstrPluginNames[i].c_str(), RTLD_LAZY);
if(handle == 0)
{
cout << "dlopen error" << endl;
return 0;
}
typedef void (*FUNC_HELP)();
FUNC_HELP dl_help = (FUNC_HELP)dlsym(handle, "Help");
if(dl_help == 0)
{
cout << "dlsym error" << endl;
return 0;
}
(dl_help)();
dlclose(handle);
}
}
return 0;
}
g++ -fpic -shared -o plugin/libfunc.so function.cpp
g++ -fpic -shared -o plugin/libfunc1.so function1.cpp
通过命令行参数 (main FUNCID)调用插件
进一步分析:
获取功能ID后,也通过命令行参数 (main FUNCID)调用插件
为了便于main调用插件,每个插件也提供GetID接口
CPluginEnumerator.cpp、CPluginEnumerator.h同上
function.cpp
#include <iostream>
using namespace std;
const int FUNC_ID = 1;
extern "C" void Print()
{
cout << "Hello World!" << endl;
}
extern "C" void Help()
{
cout << "Function ID " << FUNC_ID << " : This function will print hello world." << endl;
}
extern "C" int GetID()
{
return FUNC_ID;
}
function1.cpp
#include <iostream>
using namespace std;
const int FUNC_ID = 2;
extern "C" void Print()
{
cout << "Hello China!" << endl;
}
extern "C" void Help()
{
cout << "Function ID " << FUNC_ID << " : This function will print hello china." << endl;
}
extern "C" int GetID()
{
return FUNC_ID;
}
CPluginController.h
#ifndef CPLUGINCONTROLLER_H
#define CPLUGINCONTROLLER_H
#include <vector>
typedef void (*PROC_PRINT)(void);
typedef void (*PROC_HELP)(void);
typedef int (*PROC_GETID)(void);
class CPluginController
{
public:
CPluginController(void);
virtual ~CPluginController(void);
bool InitializeController(void);
bool UninitializeController(void);
bool ProcessHelp(void);
bool ProcessRequest(int FunctionID);
private:
std::vector<void *> m_vhForPlugin;
std::vector<PROC_PRINT> m_vPrintFunc;
std::vector<PROC_GETID> m_vGetIDFunc;
};
#endif
CPluginController.cpp
#include "CPluginController.h"
#include "CPluginEnumerator.h"
#include "dlfcn.h"
CPluginController::CPluginController(void)
{
}
CPluginController::~CPluginController(void)
{
}
bool CPluginController::InitializeController(void)
{
std::vector<std::string> vstrPluginNames;
CPluginEnumerator enumerator;
if(!enumerator.GetPluginNames(vstrPluginNames))
return false;
for(unsigned int i=0 ; i<vstrPluginNames.size(); i++)
{
void *hinstLib = dlopen(vstrPluginNames[i].c_str(), RTLD_LAZY);
if(hinstLib != NULL)
{
m_vhForPlugin.push_back(hinstLib);
PROC_PRINT DllPrint = (PROC_PRINT)dlsym(hinstLib, "Print");
PROC_GETID DllGetID = (PROC_GETID)dlsym(hinstLib, "GetID");
if((NULL != DllPrint) && (NULL != DllGetID))
{
m_vPrintFunc.push_back(DllPrint);
m_vGetIDFunc.push_back(DllGetID);
}
}
}
return true;
}
bool CPluginController::ProcessRequest(int FunctionID)
{
for(unsigned int i = 0; i < m_vGetIDFunc.size(); i++)
{
if((m_vGetIDFunc[i])() == FunctionID)
{
(m_vPrintFunc[i])();
break;
}
}
return true;
}
bool CPluginController::ProcessHelp(void)
{
std::vector<std::string> vstrPluginNames;
CPluginEnumerator enumerator;
if(!enumerator.GetPluginNames(vstrPluginNames))
return false;
for(unsigned int i=0 ; i<vstrPluginNames.size(); i++)
{
PROC_HELP DllProc;
void *hinstLib = dlopen(vstrPluginNames[i].c_str(), RTLD_LAZY);
if(hinstLib != NULL)
{
DllProc = (PROC_HELP)dlsym(hinstLib, "Help");
if(NULL != DllProc)
{
(DllProc)();
}
dlclose(hinstLib);
}
}
return true;
}
bool CPluginController::UninitializeController()
{
for(unsigned int i = 0; i < m_vhForPlugin.size(); i++)
{
dlclose(m_vhForPlugin[i]);
}
return true;
}
main.cpp
#include <iostream>
#include "CPluginController.h"
#include <string.h>
#include <stdlib.h>
using namespace std;
int main(int argc, char **argv)
{
if(argc != 2)
{
cout << "Parameters error" << endl;
return 0;
}
if(strcmp(argv[1], "help") == 0)
{
CPluginController pc;
pc.ProcessHelp();
return 0;
}
int FunctionID = atoi(argv[1]);
CPluginController pc;
pc.InitializeController();
pc.ProcessRequest(FunctionID);
pc.UninitializeController();
return 0;
}
g++ -fpic -shared -o plugin/libfunc.so function.cpp
g++ -fpic -shared -o plugin/libfunc1.so function1.cpp
g++ -o main main.cpp CPluginEnumerator.cpp CPluginController.cpp -ldl
实例4:插件设计优化–减少接口
- 版本4要求:
- 在版本3中,插件导出了Print、GetID、Help三个函数,主程序需要使用多个容器分别保存这些函数地址
- 在复杂的业务逻辑中,导出的函数可能更多,若还按照版本3的方式,代码维护性不佳
- 思路: 将三个导出函数都放在一个类中,让插件外部获取该类的对象
- 分析:
- IPrintPlugin及派生类—插件对象
- 插件调用者获得对象指针
- 仅导出一个接口函数CreateObj
- IPrintPlugin*类型的容器
- 通过插件对象指针调用
CPluginEnumerator.cpp、CPluginEnumerator.h同上
CPluginController.h
#ifndef CPLUGINCONTROLLER_H
#define CPLUGINCONTROLLER_H
#include <vector>
class IPrintPlugin;
class CPluginController
{
public:
CPluginController(void);
virtual ~CPluginController(void);
bool InitializeController(void);
bool UninitializeController(void);
bool ProcessHelp(void);
bool ProcessRequest(int FunctionID);
private:
std::vector<void *> m_vhForPlugin;
std::vector<IPrintPlugin*> m_vpPlugin;
};
#endif
CPluginController.cpp
#include "CPluginController.h"
#include "CPluginEnumerator.h"
#include "IPrintPlugin.h"
#include "dlfcn.h"
CPluginController::CPluginController(void)
{
}
CPluginController::~CPluginController(void)
{
}
bool CPluginController::InitializeController(void)
{
std::vector<std::string> vstrPluginNames;
CPluginEnumerator enumerator;
if(!enumerator.GetPluginNames(vstrPluginNames))
return false;
for(unsigned int i=0 ; i<vstrPluginNames.size(); i++)
{
typedef int (*PLUGIN_CREATE)(IPrintPlugin**);
PLUGIN_CREATE CreateProc;
IPrintPlugin *pPlugin = NULL;
void* hinstLib = dlopen(vstrPluginNames[i].c_str(), RTLD_LAZY);
if(hinstLib != NULL)
{
m_vhForPlugin.push_back(hinstLib);
CreateProc = (PLUGIN_CREATE)dlsym(hinstLib, "CreateObj");
if(NULL != CreateProc)
{
(CreateProc)(&pPlugin);
if(pPlugin != NULL)
{
m_vpPlugin.push_back(pPlugin);
}
}
}
}
return true;
}
bool CPluginController::ProcessRequest(int FunctionID)
{
for(unsigned int i = 0; i < m_vpPlugin.size(); i++)
{
if(m_vpPlugin[i]->GetID() == FunctionID)
{
m_vpPlugin[i]->Print();
break;
}
}
return true;
}
bool CPluginController::ProcessHelp(void)
{
std::vector<std::string> vstrPluginNames;
CPluginEnumerator enumerator;
if(!enumerator.GetPluginNames(vstrPluginNames))
return false;
for(unsigned int i=0 ; i<vstrPluginNames.size(); i++)
{
typedef int (*PLUGIN_CREATE)(IPrintPlugin**);
PLUGIN_CREATE CreateProc;
IPrintPlugin *pPlugin = NULL;
void* hinstLib = dlopen(vstrPluginNames[i].c_str(), RTLD_LAZY);
if(hinstLib != NULL)
{
CreateProc = (PLUGIN_CREATE)dlsym(hinstLib, "CreateObj");
if(NULL != CreateProc)
{
(CreateProc)(&pPlugin);
if(pPlugin != NULL)
{
pPlugin->Help();
}
}
dlclose(hinstLib);
}
}
return true;
}
bool CPluginController::UninitializeController()
{
for(unsigned int i = 0; i < m_vhForPlugin.size(); i++)
{
dlclose(m_vhForPlugin[i]);
}
return true;
}
IPrintPlugin.h
#pragma once
class IPrintPlugin
{
public:
IPrintPlugin();
virtual ~IPrintPlugin();
virtual void Help() = 0;
virtual void Print() = 0;
virtual int GetID() = 0;
};
IPrintPlugin.cpp
#include "IPrintPlugin.h"
IPrintPlugin::IPrintPlugin()
{
}
IPrintPlugin::~IPrintPlugin()
{
}
function.cpp
#include <iostream>
#include "IPrintPlugin.h"
using namespace std;
const int FUNC_ID = 1;
class CPrintPlugin : public IPrintPlugin
{
public:
CPrintPlugin()
{
}
virtual ~CPrintPlugin()
{
}
virtual void Print()
{
cout << "Hello World!" << endl;
}
virtual void Help()
{
cout << "Function ID " << FUNC_ID << " : This function will print hello world." << endl;
}
virtual int GetID(void)
{
return FUNC_ID;
}
};
extern "C" void CreateObj(IPrintPlugin **ppPlugin)
{
static CPrintPlugin plugin;
*ppPlugin = &plugin;
}
function1.cpp
#include <iostream>
#include "IPrintPlugin.h"
using namespace std;
const int FUNC_ID = 2;
class CPrintPlugin : public IPrintPlugin
{
public:
CPrintPlugin()
{
}
virtual ~CPrintPlugin()
{
}
virtual void Print()
{
cout << "Hello China!" << endl;
}
virtual void Help()
{
cout << "Function ID " << FUNC_ID << " : This function will print hello china." << endl;
}
virtual int GetID(void)
{
return FUNC_ID;
}
};
extern "C" void CreateObj(IPrintPlugin **ppPlugin)
{
static CPrintPlugin plugin;
*ppPlugin = &plugin;
}
main.cpp
#include <iostream>
#include "CPluginController.h"
#include <string.h>
#include <stdlib.h>
using namespace std;
int main(int argc, char **argv)
{
if(argc != 2)
{
cout << "Parameters error" << endl;
return 0;
}
if(strcmp(argv[1], "help") == 0)
{
CPluginController pc;
pc.ProcessHelp();
return 0;
}
int FunctionID = atoi(argv[1]);
CPluginController pc;
pc.InitializeController();
pc.ProcessRequest(FunctionID);
pc.UninitializeController();
return 0;
}
g++ -fpic -shared -o plugin/libfunc.so function.cpp IPrintPlugin.cpp
g++ -fpic -shared -o plugin/libfunc1.so function1.cpp IPrintPlugin.cpp
g++ -o main main.cpp CPluginEnumerator.cpp CPluginController.cpp IPrintPlugin.cpp -ldl