QtPlugin基于System Api(系统API)的dll文件动态加载方式进行插件加载。
dll 文件 两种加载方式:静态加载,动态加载。QtPlugin采用动态加载方式。
推荐一个CTK插件框架,基于QtPlugin做的封装,一个更完整的插件框架:
官方主页:http://www.commontk.org/index.php/Main_Page
GitHub源码:https://github.com/commontk/CTK
Pluma 一个更轻量级的插件框架 https://github.com/armFunNing/pluma
主要开发流程-基于Qt5:
首先定义 Interface.h (里面纯虚函数) ,格式如下:
#ifndef INTERFACE_H
#define INTERFACE_H
#include
#include
class Interface: public QObject
{
Q_OBJECT
public:
virtual ~Interface(){}
virtual void UsFunction() = 0; //纯虚函数
virtual void Init() = 0; //纯虚函数初始化
public slots:
virtual void UsPublicSlot() = 0; //纯虚函数
private slots:
virtual void UsPrivateSlot() = 0; //纯虚函数
signals:
void UsSigVoid(); //这里信号本身是没有实现方法的所以不需要虚函数
void UsSigSend(QString); //信号可以用来发送任何东西,除了QString也可以QObject下的任意子类指针
};
Q_DECLARE_INTERFACE(Interface,"FunNing.Plugin.Interface");//注册当前类为接口 参数1注册类 参数2插件身份
//Q_DECLARE_INTERFACE 来自QObject的宏 相关信息你可以查看Qt官网每一个接口的身份标识不能一致
#endif // INTERFACE_H
然后,这样符合OSGI的接口文件我们就定义出来了。(为什么要做一个接口? 这里牵扯一个抽象工厂的设计模式,我将在整个组件的最后流程进行分析)下面我将对接口文件进行实现:
Plugin.h
#ifndef PLUGIN_H
#define PLUGIN_H
#include "interface.h"
#include
#include
class Plugin:public Interface//你可以选择来自class继承的访问控制,这里我选择Public
{
Q_OBJECT
#if QT_VERSION > 0x050000 //宏定义到Qt5
Q_PLUGIN_METADATA(IID "FunNing.Demo.Plugin")//参数1:默认搭配;参数2:源信息
//"FunNing.Demo.Plugin"用于dll判断身份标识,尽量做成插件标识,
//Q_PLUGIN_METADATA(IID "FunNing.Demo.Plugin" FILE "PluginInfor.json")
//参数1:默认搭配;参数2:源信息;参数3:默认搭配;参数4:其它源信息文件,一般做成qrc文件用相对路径指向文件
Q_INTERFACES(Interface)//接口声明
#endif // QT_VERSION < 0x050000
//注:宏注册信息一定要写到类里面,不然加载插件提示找不到METADATA
public:
void UsFunction() Q_DECL_OVERRIDE;
void Init() Q_DECL_OVERRIDE;
private slots:
void UsPrivateSlot() Q_DECL_OVERRIDE;
public slots:
void UsPublicSlot() Q_DECL_OVERRIDE;
private:
QTimer* m_pTimer = new QTimer();
};
#endif // PLUGIN_H
Plugin.cpp
#include "plugin.h"
void Plugin::UsFunction()
{
//纯虚函数需要重载实现
}
void Plugin::Init()
{
m_pTimer->start(1000);
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(UsPrivateSlot()));
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(UsPublicSlot()));
}
void Plugin::UsPublicSlot()
{
qDebug()<<"this public slots output";
emit UsSigSend("Hello this Plugin message");
}
void Plugin::UsPrivateSlot()
{
qDebug()<<"this priavate slots output";
emit UsSigVoid();
}
插件内主要启动一个定时器,绑定私有槽函数和共有槽函数,出发来自插件的信号。一些插件的注意事项,我在代码中都有体现。你可以点击构建,在你生成的目录下会产生moc文件,静态库,动态库:
最后我们编写加载插件的管理者,当然你也可以省略这个类,定义成全局Class,直接在main.cpp中进行行数调用,我的工程结构如下:
PManager.h
#ifndef PMANAGER_H
#define PMANAGER_H
#include
#include
#include //插件导入类
#include
#include
#include "interface.h"
class PManager : public QObject
{
Q_OBJECT
public:
PManager(){}
~PManager()
{
if(m_pPluginIns!=nullptr) delete m_pPluginIns;
if(m_pPluginDll!=nullptr) delete m_pPluginDll;
}
void LoadPlugin(QString FilePath)
{
if(QFile(FilePath).exists())
{
qDebug()<<"load dll path:"<setFileName(FilePath)//设置dll
//m_pPluginDll->load();//饿汉加载 return is bool
QObject* Instance = m_pPluginDll->instance();//懒汉加载,官文写得很详细
if(Instance!=nullptr)
{
qDebug()<<"load Plugin successful";
qDebug()<<"metaData:"<metaData();//输出元数据信息
m_pPluginIns = qobject_cast(Instance);
connect(m_pPluginIns,SIGNAL(UsSigSend(QString)),this,SLOT(outputPluginMess(QString)));
m_pPluginIns->dumpObjectInfo();//输出类的信息
m_pPluginIns->Init();
}
else qDebug()<<"load Plugin error:"<errorString();//打印错误信息
}
else qDebug()<<"failed to select file path";
}
signals:
public slots:
void outputPluginMess(QString messStr)
{
qDebug()<<"im PManager,recv plugin mess is:" << messStr;
}
private:
QPluginLoader* m_pPluginDll= nullptr;
Interface* m_pPluginIns = nullptr;
};
#endif // PMANAGER_H
main.cpp
#include
#include "pmanager.h"
#define MyPluginPath "G:\\QtProject\\build-MPlgins-Mingw64-Debug\\debug\\MPlgins.dll"
//你的插件dll路径
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
PManager PM;
PM.LoadPlugin(MyPluginPath);
return a.exec();
}
我的运行结果:
原因:我插件中有一个QTimer的类,我把父类直接析构,子类没有先释放所造成,所以在Interface.h中除了定义接口Init函数还应该需要定义一个Destroy用于插件内部释放子类。
以上就是来自Qt的插件机制。
总结:
所谓的插件,只不过是重载了虚函数的dll,这跟抽象工厂类类似,这便是插件的原理。
在main入口函数中,我们导入Interface接口文件,不需要依赖静态库".a"和"*.lib"链接 生成代码,类似C/C++关键字extern。
而在最后我们通过系统的API加载dll,这个可以自行百度查阅 “动态库加载的两种方式”。
这样做的好处:定义开发范式,面向Interface编程,内部封装,模块和整体流程开发分离,提高开发效率。应用场景QtCreator-IDE、WPS、visual studio、Nodepad++等等,都是采用这种开发方式。