C++实现插件系统

c++ 专栏收录该内容
4 篇文章 0 订阅

 

 

文中的Qt类(Q开头的类)都可以通过STL库代替

简述

首先由开发人员编写系统框架,并预先定义好系统的扩展借口。插件由其他开发人员根据系统预定的接口编写的扩展功能,实际上就是系统的扩展功能模块。插件都是以一个独立文件的形式出现。
对于系统来说并不知道插件的具体功能,仅仅是为插件留下预定的接口,系统启动的时候根据插件的配置寻找插件,根据预定的接口把插件挂接到系统中。

插件系统所用到的技术

一般而言,构建一个插件系统所用到的步骤为
- 面相接口编程
- 插件管理器
- 动态加载链接库

应用程序提供接口

为了实现功能的扩展,必须向外部插件提供接口,在module_interface.h中定义一个抽象类ModuleInterface作为接口:

#include "plugin_register.h"
#include "module_interface.h"
class ModuleInterface
{
public:
	ModuleInterface();
	virtual ~ModuleInterface() ;
         
        //对外部插件提供的接口
	virtual int parseParamByJson(const QString &json) = 0;
	virtual QString getParamJsonStr() = 0;
	virtual int prepareParam() = 0;
	virtual int process() = 0;
};

实现插件

插件应该包含并实现应用程序提供的接口。在test.h中定义Test,让Test继承并实现ModuleInterface中提供的所有接口:

//Test.h文件
class Test : public ModuleInterface
{
public:
	Test();
	~Test();

	int prepareParam();
	int process();
	QString getParamJsonStr();
	int parseParamByJson(const QString &json);
private:
	int test_int;
};
REGISTER_CLASS(Test, ModuleInterface)//通过宏实现getObject,getName,后面会讲


//Test.cpp文件
#include "test.h"
#include <QDebug>

Test::Test()
	: test_int(0)
{
	qDebug() << "Create Test";
}

Test::~Test()
{
	if (results_->result_data)
	{
		delete[] results_->result_data;
		results_->result_data = nullptr;
	}
}

int Test::prepareParam()
{
	return 0;
}

int Test::process()
{
	return 0;
}

QString Test::getParamJsonStr()
{
	return "None";
}

int Test::parseParamByJson(const QString &json)
{
	return 0;
}

为了让应用程序动态加载插件,需要将插件编译为dll文件。在"plugin_register.h中,插件声明三个导出函数:
- getObject:用于新建一个Test对象并返回该对象的指针;
- getName:用于打印Test相关信息。
- isPlugin:用于判断是否是插件,防止其他dll文件错误导入

#pragma once
#include <QtCore/qglobal.h>

#ifndef BUILD_STATIC
#if defined(TEST_LIB) //这里需要换成对应的库的名字
#define QTCLASSLIBRARY1_EXPORT Q_DECL_EXPORT
#else
#define QTCLASSLIBRARY1_EXPORT Q_DECL_IMPORT
#endif
#else
#define QTCLASSLIBRARY1_EXPORT
#endif

//将getObject和getName封装成宏
#define CLASS_NAME(Name) #Name
#ifdef __cplusplus
#define REGISTER_CLASS(child,base)				\
extern "C" {							\
	QTCLASSLIBRARY1_EXPORT base *getObject(void)	        \
	{							\
		return new child;				\
	};							\
	QTCLASSLIBRARY1_EXPORT const char* getName(void)        \
	{							\
		return CLASS_NAME(child);			\
	};							\
	QTCLASSLIBRARY1_EXPORT bool isPlugin(void)		\
	{							\
		return true;					\
	};							\
}
#else
#define REGISTER_CLASS(child,base)				\
	QTCLASSLIBRARY1_EXPORT base *getObject(void)	        \
	{							\
		return new child;				\
	};							\
	QTCLASSLIBRARY1_EXPORT const char* getName(void)        \
	{							\
		return CLASS_NAME(child);			\
	};							\
	QTCLASSLIBRARY1_EXPORT bool* isPlugin(void)		\
	{							\
		return true;					\
	};
#endif

至此一个插件就实现了。可以按照此方式实现多个dll插件。

动态加载插件

现在来写一个应用程序,功能是加载plugins目录中的所有dll插件,打印出dll相关信息,并调用在插件中实现的函数。

//plugin.h文件
#pragma once
#include <QString>
#include <QFileInfoList>
#include <QVector>
#include <QLibrary>
#include <QDir>
#include <gt_singleton.h>
class ModuleInterface;

#define M_OBJECT ModuleInterface
#define FUN_PTR(fun_ptr_name,result_type_ptr) typedef result_type_ptr* (*fun_ptr_name)(void);
FUN_PTR(GetFunPtr, void);
FUN_PTR(GetObjFunPtr, ModuleInterface);
FUN_PTR(GetObjNameFunPtr,const char);

class Plugin
{
public:
	~Plugin();
	static Plugin& getInstance(void);//实现单例
	bool getFunObject(GetObjFunPtr& funPtr, int index);//获取实例化对象的方法
	bool getFunObjName(GetObjNameFunPtr& funPtr, int index);//获取对象名字的方法
	int getPluginNumber(void);//获取插件的数量
private:
	Plugin();
	Plugin operator=(const Plugin);
	Plugin(const Plugin&);

	QDir m_dir;
	QFileInfoList m_fileInfoList;
	QVector<QLibrary*> m_libs;

	bool getPaths(QFileInfoList& fileInfoList, QString path);//获取所有dll库的路径
	bool loadAll(QVector<QLibrary*>& libs,const QFileInfoList fileInfoList);//动态加载dll库
	bool getFun(GetFunPtr& funPtr, int index, const char* str);//获取事先定义好的dll库导出的函数getObject(),getName();
	bool isPlugin(QLibrary& lib);//判断是不是我们定义的插件库
};
//plugin.cpp文件
#include "plugins.h"
#include <QDir>
const char LIBS_PATH[] = "plugins";
const char GET_OBJECT[] = "getObject";
const char GET_OBJ_NAME[] = "getName";
const char GET_IS_PLUGIN[] = "isPlugin";

Plugin::Plugin()
{
	getPaths(m_fileInfoList, LIBS_PATH);
	loadAll(m_libs, m_fileInfoList);
}

Plugin::~Plugin()
{
	for (int i = 0; i < m_libs.size(); i++)
	{
		m_libs.at(i)->unload();
	}
	qDeleteAll(m_libs);
	m_libs.clear();
}

Plugin& Plugin::getInstance(void)
{
	static Plugin instance;
	return instance;
}

bool Plugin::getFun(GetFunPtr& funPtr, int index, const char* str)
{
	if (index >= m_libs.size())
	{
		return false;
	}
	funPtr = (GetFunPtr)m_libs.at(index)->resolve(str);//通过函数名str和索引,在m_libs中获取函数指针
	if (funPtr)
	{
		return true;
	}
	else
	{
		return false;
	}
}

bool Plugin::isPlugin(QLibrary& lib)
{
	typedef bool(*Fun_p)(void);
	Fun_p funPtr = (Fun_p)(lib.resolve(GET_IS_PLUGIN));
	if (funPtr)
	{
		return funPtr();//只有lib库中存在isPlugin,并返回值为true。才确定这是插件
	}
	return false;
}

bool Plugin::getFunObject(GetObjFunPtr& funPtr, int index)
{
	return getFun((GetFunPtr&)funPtr, index, GET_OBJECT);
}

bool Plugin::getFunObjName(GetObjNameFunPtr& funPtr, int index)
{
	return getFun((GetFunPtr&)funPtr, index, GET_OBJ_NAME);
}

int Plugin::getPluginNumber(void)
{
	return m_libs.size();
}

bool Plugin::getPaths(QFileInfoList& fileInfoList, QString path)
{
	m_dir.setPath(path);//设置文件夹路径
	QStringList filters;
	filters << "*.dll";
	m_dir.setNameFilters(filters);//设置过滤
	fileInfoList = m_dir.entryInfoList();//扫描并返回文件夹内所有dll库的路径
	if (fileInfoList.size() > 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}

bool Plugin::loadAll(QVector<QLibrary*>& libs, const QFileInfoList fileInfoList)
{
	for (int i = 0; i < fileInfoList.size(); i++)
	{//遍历所有dll文件,将正确的插件添加到libs中。
		QLibrary *lib = new QLibrary(fileInfoList.at(i).absoluteFilePath());//放入dll文件路径
		if (lib->load())//加载动态库
		{
			if (isPlugin(*lib))//如果是插件,则添加到libs中
			{
				libs.push_back(lib);
			}
			else//不是插件
			{
				lib->unload();//释放动态库
				delete lib;
			}
		}
		else//加载失败
		{
			delete lib;
		}
	}
	if (libs.size()>0)
	{
		return true;
	}
	else
	{
		return false;
	}
}

整个插件系统已经基本完成,在另一节中将使用反射将插件实例化。使用Qt、C++实现简单的反射

  • 2
    点赞
  • 1
    评论
  • 10
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值