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 
#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文件,静态库,动态库:

QtPlugin(C++跨平台插件开发)_第1张图片

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

QtPlugin(C++跨平台插件开发)_第2张图片

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();
}

我的运行结果:

QtPlugin(C++跨平台插件开发)_第3张图片

最后:

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

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

 

总结:

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

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

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

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

 

你可能感兴趣的:(Qt与GTK)