【Linux实验】4、插件框架

声明:内容出自老师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

在这里插入图片描述

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值