赞
踩
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 <QObject>
- #include <QString>
-
- 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 <QDebug>
- #include <QTimer>
-
- 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 <QObject>
- #include <QtPlugin>
- #include <QPluginLoader> //插件导入类
- #include <QFile>
- #include <QDebug>
-
- #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:"<<FilePath;
- QPluginLoader* m_pPluginDll = new QPluginLoader(FilePath);
- //m_pPluginDll->setFileName(FilePath)//设置dll
- //m_pPluginDll->load();//饿汉加载 return is bool
- QObject* Instance = m_pPluginDll->instance();//懒汉加载,官文写得很详细
- if(Instance!=nullptr)
- {
- qDebug()<<"load Plugin successful";
- qDebug()<<"metaData:"<<m_pPluginDll->metaData();//输出元数据信息
- m_pPluginIns = qobject_cast<Interface*>(Instance);
- connect(m_pPluginIns,SIGNAL(UsSigSend(QString)),this,SLOT(outputPluginMess(QString)));
- m_pPluginIns->dumpObjectInfo();//输出类的信息
- m_pPluginIns->Init();
- }
- else qDebug()<<"load Plugin error:"<<m_pPluginDll->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 <QCoreApplication>
- #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++等等,都是采用这种开发方式。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。