自定义Qt插件系统

本文介绍了插件的概念,插件系统由主系统、插件管理器和插件三部分组成,强调了插件系统在软件开发中的优势,如简化功能扩展、独立管理等。插件基类和子类的实现通过Qt的Q_DECLARE_INTERFACE宏完成,插件管理器负责扫描、加载和卸载插件。最后,文章提到了插件管理器的关键功能和测试源码的下载链接。
摘要由CSDN通过智能技术生成

一、插件概述

1.1 什么是插件

    插件(Plug-in,又称addin、add-in、addon或add-on,又译外挂)是一种遵循一定规范的应用程序接口编写出来的程序。其只能运行在程序规定的系统平台下(可能同时支持多个平台),而不能脱离指定的平台单独运行。因为插件需要调用原纯净系统提供的函数库或者数据。很多软件都有插件,插件有无数种。例如在IE中,安装相关的插件后,WEB浏览器能够直接调用插件程序,用于处理特定类型的文件。

 

1.2 插件系统系统的组成

插件系统,可以分为三部分:

  • 主系统

    通过插件管理器加载插件,并创建插件对象。一旦插件对象被创建,主系统就会获得相应的指针/引用,它可以像任何其他对象一样使用。

  • 插件管理器

    用于管理插件的生命周期,并将其暴露给主系统。它负责查找并加载插件,初始化它们,并且能够进行卸载。它还应该让主系统迭代加载的插件或注册的插件对象。

  • 插件

    插件本身应符合插件管理器协议,并提供符合主系统期望的对象。

实际上,很少能看到这样一个相对独立的分离,插件管理器通常与主系统紧密耦合,因为插件管理器需要最终提供(定制)某些类型的插件对象的实例。

 

1.3 插件系统存在的意义

一个大型的软件,架构相当复杂,如果有新的开发工程师进入团队,并参与到软件的功能扩展中,如果没有插件,那整个系统需要应该新功能而重新重新,并且新成员熟悉整个软件系统的成本也非常高,而如果有了插件系统,那将有以下优点:

  • 开发人员只需按照插件系统规范就可以添加新功能。

  • 插件和核心系统可以独立管理,相互之间并无关联。

  • 插件功能可以动态卸载和安装。

 

1.4 插件系统的基本框架

    框架图如下:

 

二、实现插件

2.1 定义插件基类

    插件基类定义插件的协议,所有继承的子类都要按照基类的协议,以下是一个简单的相机插件类:

#include <qlist.h>
#include <QtPlugin>
​
class CameraPluginBase
{
public:
    QString Name;  //相机名
​
    virtual ~CameraPluginBase(){}
    virtual bool OpenCam()= 0; //打开相机
    virtual bool CloseCam() = 0; //关闭
    virtual bool SetExposureTime(float time) = 0; //设置曝光时间
    virtual bool SetGain(float gain) = 0; //设置增益
};
​
#define CameraPluginBase_IID "Plugin.Test.Camera"
Q_DECLARE_INTERFACE(CameraPluginBase, CameraPluginBase_IID)
//注册当前类为接口 参数1注册类 参数2插件身份

    Q_DECLARE_INTERFACE是为了让Qt元对象系统知道该接口,这样主程序可以动态的扫描实现的接口文件,然后加载插件中的功能,CameraPluginBase_IID是用来标识的字符串,改字符串应该让它具有唯一性。

2.2 定义插件子类

    插件子类按照基类的协议去实现插件的相应功能,以下是一个简单的海康相机插件类:

#include <qlist.h>
#include <QtPlugin>
#include "CameraPluginBase.h"
class HikCamera : public QObjec, CameraPluginBase
{
    Q_OBJECT
    Q_INTERFACES(CameraPluginBase)
    Q_PLUGIN_METADATA(IID CameraPluginBase_IID FILE "PluginPar.json")
public:
  HikCamera();
    ~HikCamera();
    bool OpenCam();
    bool CloseCam();
    bool SetExposureTime(float time);
    bool SetGain(float gain);
};

Q_INTERFACES宏用于告诉Qt该类实现的接口。Q_PLUGIN_METADATA宏包含插件的IID,并指向一个包含插件元数据的Json文件。该Json文件会被编译到插件中,无需安装。

2.3 实现插件的元数据文件

    插件的基本功能已经实现,但是插件的数据信息却没有,有时候主程序在调用插件的时候,需要记录插件的开发者信息和开发时间,验证插件的版本信息和依赖关系,而这些数据信息都可以保存在元数据文件中,也即是上面的"PluginPar.json"文件,简单的文件如下:

{
    "author"       : "Easy Coder",
    "date"         : "2021/03/20",
    "name"         : "海康相机",
    "version"      : "1.0.0",
    "dependencies" : []
}

三、插件管理器

    插件管理有几个必须的功能:

  • 扫描所有插件,生成依赖关系。

  • 动态加载和卸载插件

3.1 插件扫描

void CameraPlugin::Scan(const QString &path)
{
    if (!QLibrary::isLibrary(path))
        return;
​
    // 获取元数据(名称、版本、依赖)
    QPluginLoader *loader = new QPluginLoader(path);
    QJsonObject    json = loader->metaData().value("MetaData").toObject();
    d->Names.insert(path, json.value("name").toVariant());
    d->Versions.insert(path, json.value("version").toVariant());
    d->Dependencies.insert(path, json.value("dependencies").toArray().toVariantList());
​
    delete loader;
    loader = nullptr;
}

3.2 插件装载

void CameraPlugin::LoadAll()
{
    // 进入插件目录
    QDir path = QDir(qApp->applicationDirPath());
    path.cd("cameras");
​
    // 初始化插件中的元数据
    foreach (QFileInfo info, path.entryInfoList(QDir::Files | QDir::NoDotAndDotDot))
        Scan(info.absoluteFilePath());
​
    // 加载插件
    foreach (QFileInfo info, path.entryInfoList(QDir::Files | QDir::NoDotAndDotDot))
        Load(info.absoluteFilePath());
}
​
void CameraPlugin::Load(const QString &path)
{
    // 判断是否是库
    if (!QLibrary::isLibrary(path))
        return;
​
    // 检测插件依赖
    if (!d->Check(path))
        return;
​
    // 加载插件
    QPluginLoader *loader = new QPluginLoader(path);
    if (loader->load()) {
        // 如果继承自 Plugin,则认为是自己的插件(防止外部插件注入)。
        CameraPluginBase *plugin = qobject_cast<CameraPluginBase *>(loader->instance());
        if (plugin) {
            d->Loaders.insert(path, loader);
            plugin->Register(d->ToolList);
        } else {
            delete loader;
            loader = Q_NULLPTR;
        }
    }
}

3.3 插件卸载

void CameraPlugin::UnLoadAll()
{
    foreach (const QString &path, d->Loaders.keys())
        Unload(path);
    for (int i = 0; i < d->ToolList.size(); ++i) {
        delete d->ToolList[i];
    }
}
​
void CameraPlugin::Unload(const QString &path)
{
    QPluginLoader *loader = d->Loaders.value(path);
​
    // 卸载插件,并从内部数据结构中移除
    if (loader->unload()) {
        d->Loaders.remove(path);
        delete loader;
        loader = Q_NULLPTR;
    }
}

 

四、总结

    要实现一个简单的插件系统并不复杂,但要实现复杂的插件系统,不仅仅要考虑插件的装载和卸载,还要考虑插件的依赖,插件的版本,插件的自动化测试,单元测试,插件的管理和维护。

    本文的测试源码已经放到csdn上,有需要的可以下载

https://download.csdn.net/download/qq_40732350/15996479

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值