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

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

 

  • 21
    点赞
  • 132
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt是一款跨平台C++应用程序开发框架,支持各种GUI(图形用户界面)和非GUI应用程序开发Qt提供了插件Plugin)机制,使开发者可以将应用程序的功能模块化,以实现灵活的插件开发Qt插件机制允许开发者将应用程序的一部分功能独立封装成插件,并在运行时动态加载。这样的好处是插件可以在不重新编译整个应用程序的情况下进行更新或替换,极大地提升了开发效率和灵活性。 插件开发中,Qt提供了一些关键的类和接口,包括QPluginLoader、QObject和Q_EXPORT_PLUGIN2等。首先,使用QPluginLoader类可以在运行时加载插件,并提供了查找和实例化插件的功能。其次,插件类需要派生自QObject类,并通过宏Q_OBJECT和Q_PLUGIN_METADATA来声明,以便Qt能够正确处理插件的元数据和信号槽机制。最后,Q_EXPORT_PLUGIN2宏用于导出插件类的实例,使其可以被QPluginLoader动态加载。 利用Qt插件机制,开发者可以将应用程序按功能划分为不同的插件模块,简化开发过程和项目维护。插件可以通过简单的配置文件进行注册和管理,实现插件的自动加载和卸载。另外,Qt的信号槽机制可以在插件之间进行通信和交互。这使得多个独立开发插件可以灵活地协同工作,提供更丰富的功能和扩展性。 总结来说,Qt插件机制使得应用程序的功能模块化,提供了灵活的插件开发方式。开发者可以通过动态加载和卸载插件,实现插件的更新和替换,提升开发效率和项目的可维护性。插件之间可以通过信号槽机制进行通信和交互,实现更丰富的功能和扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值