QT插件框架

近来学习QT的插件框架,记录一些心得

1、插件的好处:

目前有很多软件以及库都是基于插件架构,例如PS、GIS软件如Arcgis、QGIS、还比如开源图形引擎OGRE以及OSG,这些都是插件架构,通过插件架构来进行功能的扩展。

现代软件工程已经从原先的通用程序库逐步过渡到应用程序框架,比如一些C++的库,这些库都是实现某一领域特定功能的,比如GDAL,实现各种空间数据格式的解析,这种库通常不是基于插件架构;应用程序框架比如JAVA里面的三大框架。首先,假设一个场景,以C++开发应用程序为例,我们的架构是基于APP+DLL的传统架构,所有的功能糅杂在一起,这样随着系统的日益庞大,各种模块之间耦合在一起,当修改其中一个模块时,其他模块也跟着一起受到影响,假如这两个模块式不同的开发人员负责的,就需要事先沟通好,这样就造成了修改维护的困难。那怎么解决这个问题,插件架构是一种选择。那么插件架构究竟有哪些好处呢?

1、方便功能的扩展。比如在GIS引擎设计中,一般的做法是不把数据格式的解析放在GIS内核中,只是在内核中定义一些通用的数据加载解析的接口,然后通过插件来实现某一特定格式的解析,这样就可以扩展各种不同的数据格式,也方便移植。

2、更新量小。当底层的接口不变时,以插件形式存在的功能很容易独立于应用程序而更新,只需要引入新版本的插件即可。相比发布整个应用程序,这种方式的更新量小很多。

3、降低模块之间依赖,可以支持并行开发。比如两个开发人员开发不同功能的插件,他们就可以只关心自己插件功能的实现,可以实现快速开发。

4、面向未来。当你的API到达一定稳定程度后,这时候你的API可能没有更新的必要了。然而API的功能可以通过插件来进一步演化,这使得API可以再长时期内保持其可用性和适用性,使得你的API可以不被抛弃。

 

总的来讲,基于插件的设计好处很多,把扩展功能从框架中剥离出来,降低了框架的复杂度,让框架更容易实现.扩展功能与框架以一种很松的方式耦合,两者在保持接口不变的情况下,可以独立变化和发布,将软件的复杂度限制在了单个的插件之中,比较适用与需求不定或是业务容易发生变化的软件设计.

QT插件框架_第1张图片

image.png

2. 插件系统的构成

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

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

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

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

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

3 程序流

框架的基本程序流,如下所示:

QT插件框架_第2张图片

 

4、构建插件框架

4.1主程序

4.1.1接口

//step 1 定义接口
class MainInterface
{
public:
    virtual ~MainInterface(){}
    virtual QString name() = 0;
    virtual QString information() = 0;
   //返回一个Widget设置到centerwidget中进行显示  
    virtual QWidget *centerWidget() = 0; 
};
//step 2 声明接口
#define MainInterface_iid "com.Interface.MainInterface"
Q_DECLARE_INTERFACE(MainInterface, MainInterface_iid)

4.1.2 主程序加载接口

/**
 * @brief MainWindow::loadPlugins 加载插件、插件放在plugins文件夹下
 * @return 返回插件的个数
 */
int MainWindow::loadPlugins()
{
    int count  = 0;
    QDir pluginsDir = QDir(qApp->applicationDirPath());
    if(!pluginsDir.cd("plugins")) return -1;
    foreach (QString fileName, pluginsDir.entryList(QDir::Files))
    {
        QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
        QObject *plugin = pluginLoader.instance();
        if(plugin)
        {
            auto centerInterface = qobject_cast(plugin);
            if(centerInterface)
            {
                ++count;
                //加载插件后生成menu
                populateMenus(plugin,centerInterface);
            }
        }
    } 
    return count;
}

/**
 * @brief MainWindow::populateMenus 根据插件生成menu
 * @param pluginInterface 插件
 * @param i 插件实现的接口
 */
void MainWindow::populateMenus(QObject * pluginInterface,MainInterface*i )
{
    static auto menu = menuBar()->addMenu("widgets");
    auto act  = new QAction(i->name(),pluginInterface);
    //单击menu调用插件
    connect(act,&QAction::triggered,this,&MainWindow::slt_WidgetActionTriggered);
    menu->addAction(act);
}

/**
 * @brief MainWindow::slt_WidgetActionTriggered 单击menu调用插件
 */
void MainWindow::slt_WidgetActionTriggered()
{
    auto centerWidget = qobject_cast(sender()->parent())->centerWidget();
    setCentralWidget(centerWidget);
}

4.2 接口实现

class CENTERWIDGETTWOSHARED_EXPORT CenterWidgetTwo
        :public QObject
        ,public MainInterface
{
    Q_OBJECT
    //Q_INTERFACES 宏用于告诉 Qt 该类实现的接口。
    Q_INTERFACES(MainInterface)
    //Q_PLUGIN_METADATA宏用于描述插件元数据
    Q_PLUGIN_METADATA(IID MainInterface_iid) 
    
public:
    CenterWidgetTwo();
    ~CenterWidgetTwo();
    //实现虚函数
    virtual QString name() override;
    virtual QString information() override;
    virtual QWidget *centerWidget() override;
};
QWidget *CenterWidgetTwo::centerWidget()
{
    auto btn = new QPushButton("Two");
    return  btn;
}

程序截图

加载界面1

 

QT插件框架_第3张图片

 

加载界面2

QT插件框架_第4张图片

源码:

https://download.csdn.net/download/u011370855/10699687

 

 

Pluma 开源C++插件框架

http://pluma-framework.sourceforge.net/?page_id=120

 

参考:

https://blog.csdn.net/zhouxuguang236/article/details/29365261

你可能感兴趣的:(Qt)