QtPlugin(C++跨平台插件开发)

    xiaoxiao2025-02-28  27

    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++等等,都是采用这种开发方式。

     

    最新回复(0)