当前位置:   article > 正文

QtPlugin(C++跨平台插件开发)_调用qt插件

调用qt插件

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 (里面纯虚函数)  ,格式如下:

  1. #ifndef INTERFACE_H
  2. #define INTERFACE_H
  3. #include <QObject>
  4. #include <QString>
  5. class Interface: public QObject
  6. {
  7. Q_OBJECT
  8. public:
  9. virtual ~Interface(){}
  10. virtual void UsFunction() = 0; //纯虚函数
  11. virtual void Init() = 0; //纯虚函数初始化
  12. public slots:
  13. virtual void UsPublicSlot() = 0; //纯虚函数
  14. private slots:
  15. virtual void UsPrivateSlot() = 0; //纯虚函数
  16. signals:
  17. void UsSigVoid(); //这里信号本身是没有实现方法的所以不需要虚函数
  18. void UsSigSend(QString); //信号可以用来发送任何东西,除了QString也可以QObject下的任意子类指针
  19. };
  20. Q_DECLARE_INTERFACE(Interface,"FunNing.Plugin.Interface");//注册当前类为接口 参数1注册类 参数2插件身份
  21. //Q_DECLARE_INTERFACE 来自QObject的宏 相关信息你可以查看Qt官网每一个接口的身份标识不能一致
  22. #endif // INTERFACE_H

然后,这样符合OSGI的接口文件我们就定义出来了。(为什么要做一个接口? 这里牵扯一个抽象工厂的设计模式,我将在整个组件的最后流程进行分析)下面我将对接口文件进行实现:

Plugin.h

  1. #ifndef PLUGIN_H
  2. #define PLUGIN_H
  3. #include "interface.h"
  4. #include <QDebug>
  5. #include <QTimer>
  6. class Plugin:public Interface//你可以选择来自class继承的访问控制,这里我选择Public
  7. {
  8. Q_OBJECT
  9. #if QT_VERSION > 0x050000 //宏定义到Qt5
  10. Q_PLUGIN_METADATA(IID "FunNing.Demo.Plugin")//参数1:默认搭配;参数2:源信息
  11. //"FunNing.Demo.Plugin"用于dll判断身份标识,尽量做成插件标识,
  12. //Q_PLUGIN_METADATA(IID "FunNing.Demo.Plugin" FILE "PluginInfor.json")
  13. //参数1:默认搭配;参数2:源信息;参数3:默认搭配;参数4:其它源信息文件,一般做成qrc文件用相对路径指向文件
  14. Q_INTERFACES(Interface)//接口声明
  15. #endif // QT_VERSION < 0x050000
  16. //注:宏注册信息一定要写到类里面,不然加载插件提示找不到METADATA
  17. public:
  18. void UsFunction() Q_DECL_OVERRIDE;
  19. void Init() Q_DECL_OVERRIDE;
  20. private slots:
  21. void UsPrivateSlot() Q_DECL_OVERRIDE;
  22. public slots:
  23. void UsPublicSlot() Q_DECL_OVERRIDE;
  24. private:
  25. QTimer* m_pTimer = new QTimer();
  26. };
  27. #endif // PLUGIN_H

Plugin.cpp

  1. #include "plugin.h"
  2. void Plugin::UsFunction()
  3. {
  4. //纯虚函数需要重载实现
  5. }
  6. void Plugin::Init()
  7. {
  8. m_pTimer->start(1000);
  9. connect(m_pTimer,SIGNAL(timeout()),this,SLOT(UsPrivateSlot()));
  10. connect(m_pTimer,SIGNAL(timeout()),this,SLOT(UsPublicSlot()));
  11. }
  12. void Plugin::UsPublicSlot()
  13. {
  14. qDebug()<<"this public slots output";
  15. emit UsSigSend("Hello this Plugin message");
  16. }
  17. void Plugin::UsPrivateSlot()
  18. {
  19. qDebug()<<"this priavate slots output";
  20. emit UsSigVoid();
  21. }

 

插件内主要启动一个定时器,绑定私有槽函数和共有槽函数,出发来自插件的信号。一些插件的注意事项,我在代码中都有体现。你可以点击构建,在你生成的目录下会产生moc文件,静态库,动态库:

最后我们编写加载插件的管理者,当然你也可以省略这个类,定义成全局Class,直接在main.cpp中进行行数调用,我的工程结构如下:

PManager.h

  1. #ifndef PMANAGER_H
  2. #define PMANAGER_H
  3. #include <QObject>
  4. #include <QtPlugin>
  5. #include <QPluginLoader> //插件导入类
  6. #include <QFile>
  7. #include <QDebug>
  8. #include "interface.h"
  9. class PManager : public QObject
  10. {
  11. Q_OBJECT
  12. public:
  13. PManager(){}
  14. ~PManager()
  15. {
  16. if(m_pPluginIns!=nullptr) delete m_pPluginIns;
  17. if(m_pPluginDll!=nullptr) delete m_pPluginDll;
  18. }
  19. void LoadPlugin(QString FilePath)
  20. {
  21. if(QFile(FilePath).exists())
  22. {
  23. qDebug()<<"load dll path:"<<FilePath;
  24. QPluginLoader* m_pPluginDll = new QPluginLoader(FilePath);
  25. //m_pPluginDll->setFileName(FilePath)//设置dll
  26. //m_pPluginDll->load();//饿汉加载 return is bool
  27. QObject* Instance = m_pPluginDll->instance();//懒汉加载,官文写得很详细
  28. if(Instance!=nullptr)
  29. {
  30. qDebug()<<"load Plugin successful";
  31. qDebug()<<"metaData:"<<m_pPluginDll->metaData();//输出元数据信息
  32. m_pPluginIns = qobject_cast<Interface*>(Instance);
  33. connect(m_pPluginIns,SIGNAL(UsSigSend(QString)),this,SLOT(outputPluginMess(QString)));
  34. m_pPluginIns->dumpObjectInfo();//输出类的信息
  35. m_pPluginIns->Init();
  36. }
  37. else qDebug()<<"load Plugin error:"<<m_pPluginDll->errorString();//打印错误信息
  38. }
  39. else qDebug()<<"failed to select file path";
  40. }
  41. signals:
  42. public slots:
  43. void outputPluginMess(QString messStr)
  44. {
  45. qDebug()<<"im PManager,recv plugin mess is:" << messStr;
  46. }
  47. private:
  48. QPluginLoader* m_pPluginDll= nullptr;
  49. Interface* m_pPluginIns = nullptr;
  50. };
  51. #endif // PMANAGER_H

main.cpp

  1. #include <QCoreApplication>
  2. #include "pmanager.h"
  3. #define MyPluginPath "G:\\QtProject\\build-MPlgins-Mingw64-Debug\\debug\\MPlgins.dll"
  4. //你的插件dll路径
  5. int main(int argc, char *argv[])
  6. {
  7. QCoreApplication a(argc, argv);
  8. PManager PM;
  9. PM.LoadPlugin(MyPluginPath);
  10. return a.exec();
  11. }

我的运行结果:

最后:

原因:我插件中有一个QTimer的类,我把父类直接析构,子类没有先释放所造成,所以在Interface.h中除了定义接口Init函数还应该需要定义一个Destroy用于插件内部释放子类。

以上就是来自Qt的插件机制。

 

总结:

        所谓的插件,只不过是重载了虚函数的dll,这跟抽象工厂类类似,这便是插件的原理。

在main入口函数中,我们导入Interface接口文件,不需要依赖静态库".a"和"*.lib"链接 生成代码,类似C/C++关键字extern。

而在最后我们通过系统的API加载dll,这个可以自行百度查阅 “动态库加载的两种方式”。

        这样做的好处:定义开发范式,面向Interface编程,内部封装,模块和整体流程开发分离,提高开发效率。应用场景QtCreator-IDE、WPS、visual studio、Nodepad++等等,都是采用这种开发方式。

 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/112716
推荐阅读