Qt5 - 插件管理(一)

Qt5 - 插件管理(一)

最近第一次接触Qt的插件,也是第一次使用插件框架去实现一个独立的小软件,记录一下对Qt插件的理解。
我认为比较重要的概念,Qt中的插件,有两点,一是插件实质就是动态库,二是插件需要符合既定的接口标准,因为程序要识别插件,就必须制定一套接口标准供插件实现,这样在一堆动态库中加载到合法的插件。

Qt插件框架

参考了网上许多前辈的资料,自己也简单写了一个Qt插件管理框架demo,核心部分就是插件管理器(实质是一个QMap<插件名, 插件实例>的映射表)。主要功能有:

  • 从指定目录中加载合法插件
  • 直接加载指定插件
  • 卸载插件
  • 插件间消息交互/通信
    插件管理器类图

1. 插件接口

起始插件接口就好像招标一样的,我把标书给出来,符合要求的厂家我才要。iPluginInterface就是标书,我的插件管理器要求每个合格的插件必须实现iPluginInterface中的接口,这样我才能保证能从插件中获取想要的信息。
对于插件接口的规定:

a. uniId,每个插件实例都需要有一个唯一的标识,这个标识是实例唯一,而不是插件唯一,是因为同一个插件在同一进程中可能会被加载多次(插件复用),因此需要有一个针对每个插件实例的唯一编号,方便查找、操作对应的插件实例。
b. 插件名,因为uniId可能仅仅是一个编码,为了更直观地看出插件信息,规定了插件名。
c. 插件描述信息。
d. 因为我这里的插件接口是控件接口,所以我规定每个插件都会有一个主控件centerWidget。
e. 为了实现插件间的通信,以及尽可能使用Qt自身的信号与槽机制,接口声明了一个customConnect()、一个信号sg_iPluginInterface_signal()以及一个槽函数sl_iPluginInterface_slot()。以前写QT都一直以为信号与槽不能定义接口函数,这次学到并且实践了用接口(纯虚函数)定义信号和槽的方法。

/**
 * @file   iplugininterface.h
 * @brief  自定义插件框架接口
 * @note   1. 如果自定义插件需要使用此框架的插件管理器,插件必须实现此接口
 *         2. 为了支持静态插件加载,每个插件实现必须在构造函数中使用Q_INIT_RESOURCE静态导入资源
 *              TestPluginInstance1::TestPluginInstance1()
 *              {
 *              #ifdef BUILD_QT_STATIC
 *                  Q_INIT_RESOURCE(testplugin1);
 *              #endif
 *
 *                  translate(":/translate/testplugin1.qm");
 *              }
 *
 *         3. 导入多个静态插件时,建议在main()函数处统一导入,宏括号内是插件实现的类名
 *              #ifdef BUILD_QT_STATIC
 *              Q_IMPORT_PLUGIN(TestPluginInstance1)
 *              Q_IMPORT_PLUGIN(TestPluginInstance2)
 *              #endif
 *
 *         4. 插件实现中用到的.json配置文件必须和插件实现的源文件在同一目录
 *
 * @author lixingye
 * @date   2021-09-13
 */

#ifndef IPLUGININTERFACE_H
#define IPLUGININTERFACE_H

#include "ipluginroute.h"
#include "qt_plugin_def.h"

#include <QObject>
#include <QTranslator>

namespace x_qtplugin
{

class BASE_PLUGIN_API iPluginInterface
{
private:
    QString             m_id;
    QString             m_name;
    QString             m_description;

    iPluginInterface   *m_pParent;
    iPluginRoute       *m_pRoute;

    QTranslator        *m_pTranslator;

public:
    X_CLASS_INFO()

    iPluginInterface();

    /**
     * @brief       设置插件唯一标识
     * @param [in]  id      - 插件唯一标识
     */
    virtual void        setUniId(const QString &id);

    /**
     * @brief       获取插件唯一标识
     *
     * @return      插件唯一标识符
     */
    virtual QString     uniId();

    /**
     * @brief       设置插件名称
     * @note        默认值是类名,如果调用此函数重新设置,则为新名字
     * @param [in]  name    - 插件名
     */
    virtual void        setName(const QString &name);

    /**
     * @brief       获取插件名
     * @note        以类名作为插件名
     *
     * @return      插件名
     */
    virtual QString     name();

    /**
     * @brief       设置插件描述信息
     * @param [in]  descrip     - 插件描述信息
     */
    virtual void        setDescription(const QString &descrip);

    /**
     * @brief       获取插件描述信息
     *
     * @return      插件描述信息
     */
    virtual QString     description();

    /**
     * @brief       设置父插件
     * @param [in]  pParent     - 父插件
     */
    virtual void        setParent(iPluginInterface *pParent);

    /**
     * @brief       加载翻译文件
     * @param[in]   翻译文件所在目录
     *
     * @return
     *      - -1    - 翻译文件加载失败
     *      -  0    - 翻译文件加载成功
     */
    virtual int         translate(const QString &dir);

    /**
     * @brief       插件将自身的信号sg_iPluginInterface_signal连接到目标对象的槽函数
     * @note        由于QT的connect需要QObject以及声明Q_OBJECT宏,所以此函数留给具体插件实现
     *
     * @param[in]   receiver    - 中转对象/路由对象指针
     * @param[in]   pRecSlot    - 中转对象/路由对象的槽函数
     * @param[in]   ifConnect   - 建立连接/解除连接标志,true为建立连接,false为解除连接
     *
     * @return
     *      - false - 建立连接失败
     *      - true  - 建立连接成功
     *
     * @note        receiver指示的标对象一般为路由器,作中转信号作用,用于实现插件间的通信
     *              receiver通常是直接归属Qt继承体系的类,如继承QObject
     */
    virtual bool        customConnect(QObject *receiver, const char *pRecSlot, bool ifConnect = true)   = 0;

    /**
     * @brief       启用路由功能
     * @note        customConnect 的简易版,交由具体插件实现
     * @param [in]  pRoute  - 路由器
     */
    virtual void        enableRoute(iPluginRoute *pRoute)                                               = 0;

    /**
     * @brief       获取插件
     *
     * @return
     *      - NULL  - 插件获取失败
     *      - !NULL - 插件获取成功
     */
    virtual QWidget *   centerWidget()                                                                  = 0;

    /**
     * @brief       插件信号/消息
     * @param[in]   msg - 统一的消息结构,详情参考 @ret ipluginroute.h中的PluginMsg类
     *
     * @note        define a signal in interface
     *              以纯虚函数形式声明一个信号,在实现类中需要使用Qt的signals:进行修饰
     */
    virtual void        sg_iPluginInterface_signal(PluginMsg msg)                                       = 0;

    /* define a slot in interface, will be called by router directly */
    /**
     * @brief       插件槽函数
     * @param[in]   msg - 统一的消息结构,详情参考 @ret ipluginroute.h中的PluginMsg类
     *
     * @note        define a slot in interface, will be called by router directly
     *              此槽函数不需要使用Qt的connect()函数与信号链接,也不能直接被connect()函数链接,
     *              否则会提示"...slot not found/not defined"
     *              而是直接由路由器调用。
     */
    virtual void        sl_iPluginInterface_slot(PluginMsg msg)                                         = 0;

protected:
    virtual ~iPluginInterface() {}
};

}

/* 如果加上命名空间,以下两句代码不能在命名空间内部,且类名需要写为"namespace::iPluginInterface" */
#define IPLUGININTERFACE_iid "com.Plugin.iPluginInterface"
Q_DECLARE_INTERFACE(x_qtplugin::iPluginInterface, IPLUGININTERFACE_iid)

#endif // IPLUGININTERFACE_H

2. 插件管理器

我的插件管理器比较简陋,只有三个功能:加载、卸载和获取插件实例。插件管理器内部会使用QMap<QString, QPluginLoader*>建立插件名到插件的映射(其实这里为了适应同一插件多实例的场景,应该建立uniId->插件实例的映射,只是我目前的项目没用到,偷了个懒),而插件管理器自身继承自QStringList,保存已加载的各个插件名,比较方便查看已经加载了哪些插件。

如果没有插件间数据交互的需要,实现插件管理器之后就可以使用这个插件框架了。

/**
 * @file   pluginsmanager.h
 * @brief
 * @note   目前实现的插件间通信是需要一个直接继承QObject或者是间接继承QObject的类作为中转的,插件间不能直接通信
 *         兼容静态插件加载和动态插件加载
 *
 * @ref    https://blog.csdn.net/yizhou2010/article/details/79961554
 *         https://www.cnblogs.com/newstart/p/3457072.html?spm=a2c6h.12873639.0.0.ef5335d1Yrgn2Y
 *         https://blog.csdn.net/kenfan1647/article/details/107493294
 *         https://www.tqwba.com/x_d/jishu/218926.html  // 这篇讲的很透彻
 *
 *         // custom interface
 *         https://www.cnblogs.com/cheungxiongwei/p/11796839.html
 *
 * @author lixingye
 * @date   2021-09-13
 */

#ifndef PLUGINSMANAGER_H
#define PLUGINSMANAGER_H

#include "qt_plugin_def.h"

#include <QObject>
#include <QPluginLoader>
#include <QMap>

#ifdef Q_OS_WIN
    #ifdef QT_DEBUG
        #define PLUGIN_LIB_NAME(name) (name + "d.dll")
    #else
        #define PLUGIN_LIB_NAME(name) (name + ".dll")
    #endif
#else
    #ifdef QT_DEBUG
        #define PLUGIN_LIB_NAME(name) ("lib" + name + "d.so")
    #else
        #define PLUGIN_LIB_NAME(name) ("lib" + name + ".so")
    #endif
#endif

namespace x_qtplugin
{

class iPluginRoute;

class BASE_PLUGIN_API PluginsManager : public QObject, public QStringList
{
    Q_OBJECT
private:
    QMap<QString, QPluginLoader *> m_plugins;           /**< here is better to save QPluginLoader or QObject(concrect plugin) ? */
    // QMap<QString, QObject *>       m_plugins;

    iPluginRoute                  *m_pRoute;            /**< 为了接口更好用,引入与路由的双向依赖 */

public:
    PluginsManager(iPluginRoute *pRoute, QObject *parent = 0);
    virtual ~PluginsManager();

    /**
     * @brief       从插件目录中加载指定插件
     * @param[in]   pluginDir   - 插件目录
     * @param[in]   pluginName  - 指定插件名, 带.so后缀以及lib前缀
     *
     * @note        目前对某一插件,仅能加载一个实例
     *              且传入的plugiName参数统一转换为小写之后,必须和插件.json配置文件中的"Keys"一致
     *              .json配置文件可用于判断插件合法性
     *
     * @attention   该实现尚未完善处理动态加载debug还是release区分
     *
     * @return 
     *      - false - 加载失败
     *      - trur  - 加载成功
     */
    virtual bool load(const QString &pluginDir, const QString &pluginName);

    /**
     * @brief       从插件目录中加载所有插件
     * @note        一般来说目录中最好至存放插件,否则推荐调用带插件名的load接口,可以指定插件,确保插件合法性
     * @param[in]   pluginDir   - 插件目录
     *
     * @return
     *      - false - 加载失败
     *      - true  - 加载成功
     */
    virtual bool load(const QString &pluginsDir);

    /**
     * @brief       从插件管理器中卸载指定插件
     * @param[in]   pluginName  - 指定插件名
     *
     * @return 
     *      - false - 卸载失败
     *      - true  - 卸载成功
     */
    virtual bool unload(const QString &pluginName);

    /**
     * @brief       卸载插件管理器中的所有插件
     */
    virtual void unload();

    /**
     * @brief       根据插件名从插件管理器中获取已加载插件的一个实例
     * @param[in]   pluginName - 指定插件名
     *
     * @return
     *      - NULL  - 获取插件实例失败
     *      - !NULL - 获取插件实例成功
     *
     * @note        每一个插件仅能获取一个实例,目前不支持同一插件多实例获取
     */
    virtual QObject * getPlugin(const QString &pluginName);
};

}

#endif // PLUGINSMANAGER_H

3. 插件间路由

因为之前尝试过Qt插件间不能直接进行数据交换,使用全局变量是可以的,但是总感觉使用全局变量的路子不太对,所以后面查了一下插件间可以通过插件管理器作路由来转发数据/消息,这种方法比较麻烦,不如全局变量来得快,也算是多积累一种方法吧。

由于我的插件接口没有继承QObject,因为不想连一个接口内部都带着Qt的QObject,感觉这样接口有点“胖”。所以不能直接使用Qt的信号与槽,只能另辟蹊径。消息通信嘛,需要一个协议,也就是相对统一的消息结构,然后直到消息的来源去向,基本上就可以实现了,消息接收方自行根据来源和数据解析消息,然后进行相应操作即可。

/**
 * @file   ipluginroute.h
 * @brief  插件框架的插件消息路由接口定义以及统一消息类定义
 * @note
 *
 * @author lixingye
 * @date   2021-09-13
 */

#ifndef IPLUGINROUTE_H
#define IPLUGINROUTE_H

#include "qt_plugin_def.h"

#include <QString>
#include <QMap>
#include <QVariant>

namespace x_qtplugin
{

class BASE_PLUGIN_API PluginMsg
{
private:
    QString m_srcPlugin;                    /**< 消息来源,发送消息的插件名 */  
    QString m_dstPlugin;                    /**< 消息去向,接收消息的插件名 */

    QMap<QString, QVariant> m_parameters;   /**< key-val pair: <name, val> */
    QObject                *m_pObject;      /**< 可以是发送消息插件的一些部件指针,如QWidget *等,由接收消息插件处理消息后直接使用,也可以是一些大型数据不能通过作为信号参数发送的 */

public:
    inline QString & srcPlugin() { return m_srcPlugin; }
    inline QString & dstPlugin() { return m_dstPlugin; }

    inline QMap<QString, QVariant> & paramters() { return m_parameters; }
    inline QObject *& object() { return m_pObject; }
};

class PluginsManager;

/**
 * @brief   插件消息路由类
 * @note    接口实现与插件管理器形成双向依赖
 */
class BASE_PLUGIN_API iPluginRoute
{
public:

    static iPluginRoute * newRoute();
    static void           delRoute(iPluginRoute *pRoute);

    /**
     * @brief       为路由设置插件管理器,路由依靠插件管理器查找目标插件
     * @param [in]  pManager    - 插件管理器对象指针,值为NULL时复位插件管理器设置,路由无效
     */
    virtual void setPluginManager(PluginsManager *pManager) = 0;

    /**
     * @brief       根据消息中的来源和去向实现消息转发
     * @note        实际上是一个槽函数,供插件连接
     */
    virtual void route(PluginMsg msg)                       = 0;

protected:
    virtual ~iPluginRoute() {}
};

}

// #define IPLUGINROUTE_IID "com.Plugin.PluginRoute"
// Q_DECLARE_INTERFACE(iPluginRoute, IPLUGINROUTE_IID)

#endif // IPLUGINROUTE_H

4. 完整代码

/**
 * @file   qt_plugin_def.h
 * @brief  qt插件管理框架使用到的公共定义
 * @note
 *
 * @author lixingye
 * @date   2021-10-27
 */

#ifndef QT_PLUGIN_DEF_H
#define QT_PLUGIN_DEF_H

#include <typeinfo>
#include <cxxabi.h>

#if defined(BASE_PLUGIN_STATICLIB)
    #define BASE_PLUGIN_API
#elif defined(WIN32) || defined(_WIN32)
    #if defined(BASE_PLUGIN_EXPORTS)
        #define BASE_PLUGIN_API __declspec(dllexport)
    #else
        #define BASE_PLUGIN_API __declspec(dllimport)
    #endif
#else
    #define BASE_PLUGIN_API __attribute__((visibility("default")))
#endif

namespace x_qtplugin
{

/**
 * @note    获取类名的宏
 *          此宏需要在public可见性之后声明
 *
 *          使用方式:
 *              public:
 *                  X_CLASS_INFO()  // 这里不用加分号,部分ide会报错或者警告
 */
#define X_CLASS_INFO_GCC() \
    int status = -1;                                                                            \
    char *demangledName = abi::__cxa_demangle(typeid(*this).name(), NULL, NULL, &status);       \
    if (status == 0)                                                                            \
    {                                                                                           \
        resName = demangledName;                                                                \
        ::free(demangledName);                                                                  \
    }

#define X_CLASS_INFO_MSVC() \
    resName     = typeid(*this).name();                                                         \
    int index   = resName.find(' ');                                                            \
    if (index != resName.nops)                                                                  \
    {                                                                                           \
        resName.assign(resName, index + 1, resName.length());                                   \
    }

#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG)
#define X_CLASS_INFO()                                                                          \
    virtual std::string classname()                                                             \
    {                                                                                           \
        std::string resName = "";                                                               \
        X_CLASS_INFO_GCC();                                                                     \
        return resName;                                                                         \
    }
#else
#define X_CLASS_INFO()                                                                          \
    virtual std::string classname()                                                             \
    {                                                                                           \
        std::string resName = "";                                                               \
        X_CLASS_INFO_MSVC();                                                                    \
        return resName;                                                                         \
    }
#endif

}

#endif // QT_PLUGIN_DEF_H

// iplugininterface.cpp 头文件在本文上边
#include "iplugininterface.h"
#include <QApplication>

namespace x_qtplugin
{

iPluginInterface::iPluginInterface()
    : m_id(""), m_description(""),
      m_pParent(NULL), m_pRoute(NULL),
      m_pTranslator(NULL)
{
    m_name = QString::fromStdString(classname());
}

void iPluginInterface::setUniId(const QString &id)
{
    m_id = id;
}

QString iPluginInterface::uniId()
{
    return m_id;
}

void iPluginInterface::setName(const QString &name)
{
    m_name = name;
}

QString iPluginInterface::name()
{
    return m_name;
}

void iPluginInterface::setDescription(const QString &descrip)
{
    m_description = descrip;
}

QString iPluginInterface::description()
{
    return m_description;
}

void iPluginInterface::setParent(iPluginInterface *pParent)
{
    m_pParent = pParent;
}

int iPluginInterface::translate(const QString &dir)
{
    if (m_pTranslator == NULL)
    {
        m_pTranslator = new QTranslator();
    }

    if (dir.compare("") == 0)
    {
        return -1;
    }

    if (!m_pTranslator->load(dir))
    {
        return -1;
    }

    return (qApp->installTranslator(m_pTranslator) ? 0 : -1);
}

}

/**
 * @file   pluginsmanager.cpp  头文件在本文上边
 * @brief
 * @note
 *
 * @author lixingye
 * @date   2021-09-11
 */

#include "pluginsmanager.h"
#include "ipluginroute.h"
#include "iplugininterface.h"

#include <QDir>
#include <QDebug>
#include <QJsonArray>

namespace x_qtplugin
{

PluginsManager::PluginsManager(iPluginRoute *pRoute, QObject *parent)
    : m_pRoute(pRoute)
{
    Q_UNUSED(parent);
    clear();

    m_pRoute->setPluginManager(this);
}

PluginsManager::~PluginsManager()
{
    unload();
}

bool PluginsManager::load(const QString &pluginDir, const QString &pluginName)
{
#ifdef BUILD_QT_STATIC
    QVector<QStaticPlugin> plugins  = QPluginLoader::staticPlugins();

    QString _pluginName             = "";
    QObject * pRes                  = nullptr;
    iUpgUiPlugin * plugin           = nullptr;

    for (int i = 0; i < plugins.size(); ++i)
    {
        QJsonObject meta = plugins[i].metaData().value("MetaData").toObject();
        QString name     = meta.value("Keys").toArray().at(0).toString();

        if ((name != "") && pluginName.contains(name, Qt::CaseInsensitive))
        {
            plugin = qobject_cast<iUpgUiPlugin *>(plugins[i].instance());
            _pluginName = name;

            break;
        }
    }

    if (plugin == nullptr)
    {
        qDebug() << "static load plugin failed: " << _pluginName;
        return false;
    }
#else
    QDir plugins(pluginDir);
    if (!plugins.exists())
    {
        return false;
    }

    QString _pluginName = pluginName;
    if (!QLibrary::isLibrary(_pluginName))
    {
        _pluginName = PLUGIN_LIB_NAME(pluginName);
    }

    QPluginLoader *pLoader = new QPluginLoader(plugins.absoluteFilePath(_pluginName));
    QObject       *pPlugin = pLoader->instance();
    QJsonObject    meta    = pLoader->metaData().value("MetaData").toObject();
    QString        key     = meta.value("Keys").toArray().at(0).toString();
    if ((key.compare("") == 0) || !_pluginName.contains(key, Qt::CaseInsensitive))
    {
        qDebug() << "load plugin failed: " << _pluginName << ", err: please add 'Keys' value into " + _pluginName + ".json";
        return false;
    }

    if (!pLoader->load() || !pPlugin)
    {
        qDebug() << "load plugin failed: " << _pluginName << ", err: " << pLoader->errorString();
        return false;
    }

    /* make sure there is only one plugin instance in process */
    this->unload(_pluginName);

    m_plugins.insert(_pluginName, pLoader);

    auto plugin = qobject_cast<iPluginInterface *>(pLoader->instance());

#endif

    this->append(_pluginName);

    /* 去重复 */
    this->toSet().toList();

    if (m_pRoute != NULL)
    {
        // plugin->enableRoute(m_pRoute);
        plugin->customConnect(dynamic_cast<QObject *>(m_pRoute), SLOT(route(PluginMsg)));
    }

    qDebug() << "load plugin successfully: " << _pluginName;

    return true;
}

bool PluginsManager::load(const QString &pluginsDir)
{
    QDir plugins(pluginsDir);
    if (!plugins.exists())
    {
        return false;
    }

    foreach (const QString &pluginName, plugins.entryList(QDir::Files))
    {
        load(pluginsDir, pluginName);
    }

    return true;
}

bool PluginsManager::unload(const QString &pluginName)
{
#ifdef BUILD_QT_STATIC

#else
    QString _pluginName = pluginName;
    if (!QLibrary::isLibrary(_pluginName))
    {
        _pluginName = PLUGIN_LIB_NAME(pluginName);
    }

    QMap<QString, QPluginLoader *>::iterator iter = m_plugins.find(_pluginName);
    if (iter == m_plugins.end())
    {
        // qDebug() << "unload plugin failed: " << _pluginName << ", err: not found the plugin.";
        return false;
    }

    QPluginLoader *pLoader = iter.value();
    if (!pLoader->unload())
    {
        qDebug() << "unload plugin failed: " << _pluginName << ", err: " << pLoader->errorString();
        return false;
    }

    m_plugins.erase(iter);
    delete pLoader;
    pLoader = nullptr;

    int index = -1;
    while ((index = this->indexOf(_pluginName)) >= 0)
    {
        this->removeAt(index);
        index = -1;
    }

#endif

    return true;
}

void PluginsManager::unload()
{
    foreach (const QString &pluginName, m_plugins.keys())
    {
        this->unload(pluginName);
    }

    this->clear();
}

QObject * PluginsManager::getPlugin(const QString &pluginName)
{ 
#ifdef BUILD_QT_STATIC
    QVector<QStaticPlugin> plugins = QPluginLoader::staticPlugins();
    QObject * pRes = nullptr;
    for (int i = 0; i < plugins.size(); ++i)
    {
        QJsonObject meta = plugins[i].metaData().value("MetaData").toObject();
        QString name     = "";
        name             = meta.value("Keys").toArray().at(0).toString();

        if (pluginName == name)
        {
            pRes = plugins[i].instance();
            break;
        }
    }

#else

    QString _pluginName = pluginName;
    if (!QLibrary::isLibrary(_pluginName))
    {
        _pluginName = PLUGIN_LIB_NAME(pluginName);
    }

    QMap<QString, QPluginLoader *>::iterator iter = m_plugins.find(_pluginName);
    if (iter == m_plugins.end())
    {
        qDebug() << "plugin not exist: " << _pluginName;
        return nullptr;
    }

    if (!iter.value()->isLoaded())
    {
        return nullptr;
    }

    QObject *pRes = iter.value()->instance();

    if (pRes == nullptr)
    {
        qDebug() << "get plugin failed, err: " << iter.value()->errorString();
    }

#endif

    return pRes;
}

}

// ipluginroute.cpp 头文件在本文上边
#include "ipluginroute.h"
#include "pluginroute.h"

namespace x_qtplugin
{

iPluginRoute * iPluginRoute::newRoute()
{
    return (new PluginRoute());
#if 0
    static PluginRoute router(pPluginsManager);
    return &router;
#endif
}

void iPluginRoute::delRoute(iPluginRoute *pRoute)
{
    if (pRoute)
    {
        delete (PluginRoute *)pRoute;
    }
}

}
/**
 * @file   pluginroute.h
 * @brief  插件路由器实现
 * @note
 *
 * @author lixingye
 * @date   2021-10-27
 */

#ifndef PLUGINROUTE_H
#define PLUGINROUTE_H

#include <QObject>
#include "ipluginroute.h"
#include "pluginsmanager.h"

namespace x_qtplugin
{

class PluginRoute : public QObject, public iPluginRoute
{
    Q_OBJECT
    // Q_INTERFACES(iPluginRoute)

private:
    PluginsManager * m_pPluginsManager;

public:
    PluginRoute();
    virtual ~PluginRoute();

protected:
    virtual void setPluginManager(PluginsManager *pManager) override;

public slots:
    virtual void route(PluginMsg msg)        override;
};

}

#endif // PLUGINROUTE_H

#include "pluginroute.h"
#include "iplugininterface.h"
#include <QDebug>

namespace x_qtplugin
{

PluginRoute::PluginRoute()
    : m_pPluginsManager(NULL)
{

}

PluginRoute::~PluginRoute()
{

}

void PluginRoute::setPluginManager(PluginsManager *pManager)
{
    m_pPluginsManager = pManager;
}

void PluginRoute::route(PluginMsg msg)
{
    qDebug() << "plugin route received: " << msg.srcPlugin();

    if (!m_pPluginsManager)
    {
        qDebug() << "route failed, err: plugins manager is null";
        return;
    }

    QObject * pObj = m_pPluginsManager->getPlugin(msg.dstPlugin());
    if (pObj)
    {
        iPluginInterface *pPLugin = qobject_cast<iPluginInterface *>(pObj);
        pPLugin->sl_iPluginInterface_slot(msg);
    }
}

}

QMake工程

我的Qt版本是5.9.4,我在centos7和windows都已经编译通过了使用时,需要注意根据自身情况修改工程目标生成目录,如tmp_api.path、tmp_lib.path以及target.path。

QT                             += core gui widgets

TEMPLATE                        = lib

# 一般linux下qmake生成的目标库会有4个,原因大概是利用软链接达到更换lib*.so版本更便捷,只需要修改lib*.so的指向
# 如果不想生成4个.so.*文件,可以使用CONFIG += plugin
CONFIG                         += plugin

CONFIG(debug, debug|release) {
BUILD_POSTFIX                   = "d"
BUILD_TYPE                      = "debug"       # 这里获取构件类型是因为windows下编译产生的临时目录比linux多了一级,在编译时拷贝操作会用到
}
else {
BUILD_POSTFIX                   = ""
BUILD_TYPE                      = "release"
}

# TARGET在Debug下自动会带'd'后缀,但是使用了自定义的TARGET名后不会自动加'd'
TARGET                          = plugin_manager$${BUILD_POSTFIX} # 注意,这里不等价 target.files += plugin_managerd

# 为了跨平台,会使用qmake条件判断,qmake中关于平台相关的判断变量有win32/unix/macx
win32 {
    DEFINES                    += BASE_PLUGIN_EXPORTS
}

INCLUDEPATH                    +=                           \
                                  api                       \
                                  src                       \

HEADERS                        +=                           \
                                  api/pluginsmanager.h      \
                                  api/ipluginroute.h        \
                                  src/pluginroute.h         \
                                  api/iplugininterface.h    \
                                  api/qt_plugin_def.h

SOURCES                        +=                           \
                                  src/pluginsmanager.cpp    \
                                  src/pluginroute.cpp       \
                                  src/ipluginroute.cpp      \
                                  src/iplugininterface.cpp

# 编译时拷贝接口api文件
tmp_api.files                  += $${PWD}/api/*.h
tmp_api.path                    = $${PWD}/../../compile_api

# 编译时拷贝生成库
unix {
    tmp_lib.files              += $${OUT_PWD}/lib$${TARGET}.so
}

win32 {
    tmp_lib.files              += $${OUT_PWD}/$${BUILD_TYPE}/$${TARGET}.dll
}

tmp_lib.path                    = $${PWD}/../../compile_lib

unix {
    copy_api                    = $${QMAKE_COPY_FILE} $${tmp_api.files} $${tmp_api.path}
    copy_libs                   = $${QMAKE_COPY_FILE} $${tmp_lib.files} $${tmp_lib.path}

    QMAKE_POST_LINK            += $$copy_libs; $$copy_api
}

win32 {
    # 由于windows命令行大多数命令都不支持路径中使用'/',需要将路径转换为'\\',replace()内置函数可以通过"qtcreator->帮助->索引"进行搜索
    tmp_api.files               = $$replace(tmp_api.files, /, \\)
    tmp_api.path                = $$replace(tmp_api.path, /, \\)
    tmp_lib.files               = $$replace(tmp_lib.files, /, \\)
    tmp_lib.path                = $$replace(tmp_lib.path, /, \\)

    # windows下如果目标目录不存在可能会出问题,所以还可以先判断目录是否存在,若不存在则创建,其实linux下也可以多加这一步
    mkdir_inc                   = (if not exist $${tmp_api.path} $${QMAKE_MKDIR} $${tmp_api.path})
    mkdir_lib                   = (if not exist $${tmp_lib.path} $${QMAKE_MKDIR} $${tmp_lib.path})

    copy_api                    = $${QMAKE_COPY_FILE} $${tmp_api.files} $${tmp_api.path}
    copy_libs                   = $${QMAKE_COPY_FILE} $${tmp_lib.files} $${tmp_lib.path}

    QMAKE_POST_LINK            += $$copy_libs && $$copy_api
}

# target应该是qmake内置变量,当TARGET模板是库时,使用target.path指定安装路径
# https://doc.qt.io/archives/qt-4.8/qmake-variable-reference.html#target
# https://doc.qt.io/archives/qt-4.8/qmake-variable-reference.html#destdir
# 官网中两个变量TARGET和DESTDIR分别是设置target.files和target.path的,但是TARGET不等价与target.files
target.path                     = $${PWD}/../../lib # 等价于 DESTDIR         = $${PWD}/../../lib

# 一般库是需要提供接口的,所以api也许要安装到指定目录,也可以直接在编译时就把api拷贝到最终目录
apis.files                     += $${PWD}/api/*
apis.path                       = $${PWD}/../../api/plugin_manager

INSTALLS                       += target apis

偷得浮生半日闲,又摘桃花换酒钱!现在的我已经是废废的了,做啥都抬不起劲,连游戏都不想打了。。。睡了!

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 基于Qt插件插件管理器,是一种用于管理Qt插件的工具。Qt插件是一组动态链接库,可用于扩展Qt应用程序的功能。插件管理器可以帮助用户安装、卸载、启用或禁用Qt插件,从而方便用户管理插件。下面将简单介绍基于Qt插件插件管理器。 首先,基于Qt插件插件管理器通常会将所有可用的Qt插件列出来,并提供插件的基本信息,如插件名称、版本、作者、描述等。用户可以根据这些信息选择需要的插件,并进行相应的管理操作。例如,用户可以通过插件管理器安装一个新的插件,也可以卸载一个已经安装的插件,还可以启用或禁用某个插件。 其次,基于Qt插件插件管理器还可以提供插件的分类和搜索功能。插件通常根据其功能或类型进行分类,如UI插件、数据存储插件、网络插件等。用户可以根据自己的需求进行分类查找,并在插件管理器中进行搜索。这样可以更快更方便地找到所需的插件。 最后,基于Qt插件插件管理器还可提供插件更新和版本控制功能。由于插件通常是由不同的开发人员编写的,因此新的功能、 bug修复或性能优化常常是通过更新插件来实现的。插件管理器可以帮助用户自动检测更新,并进行提示和下载。同时,插件管理器也可提供版本控制功能,为用户提供更好的插件管理服务。 综上所述,基于Qt插件插件管理器是一种方便用户管理Qt插件的工具,可提供插件列举、分类、搜索、更新和版本控制等功能。它对于Qt插件开发和使用者来说都有重要的价值。 ### 回答2: 基于Qt插件插件管理器是一种软件工具,它可以通过使用Qt插件技术来方便地实现动态加载和卸载插件,从而使应用程序具备可拓展性、易维护性和可定制性。该插件管理器可以对插件进行查找、加载、安装、更新、卸载等操作,让应用程序的功能更加灵活多样化。 基于Qt插件插件管理器利用Qt动态链接库技术实现插件的运行时连接,从而实现插件动态加载和卸载。在此过程中管理器会读取插件的元数据信息,例如插件名称、版本信息、作者、依赖关系等,并且可以通过接口调用插件的函数、方法和信号来完成具体的操作。 在插件管理器中,插件通常以动态链接库文件的形式存在,它们被保存在特定目录下。管理器可以根据插件元数据信息查找需要的插件并进行加载。此外,插件之间可能会存在一些依赖关系,例如插件A依赖于插件B,此时管理器会自动加载插件B并进行初始化,以确保插件A的正常运行。 总之,基于Qt插件插件管理器是一种可帮助我们实现插件集成和管理的工具。它可以通过动态加载和卸载插件来增强应用程序的拓展性,从而更好地满足用户的需求。同时,插件管理器的实现也可以让我们更好地理解Qt插件技术的原理及应用。 ### 回答3: 基于Qt插件插件管理器是一种可扩展的软件架构,它允许在运行时动态加载、卸载和管理Qt插件。这种插件管理器可以在Qt应用程序或框架中使用,以便扩展其功能或通过模块化的方式组织其功能。 Qt插件是通过一组接口和元数据定义的,这些接口描述了插件提供的服务、插件依赖的服务和插件如何被加载。插件管理器根据这些元数据和接口来扫描插件目录,加载和初始化需要的插件。通过这种方式,插件管理器可以帮助开发人员方便地将新功能添加到应用程序中,而无需修改应用程序的源代码。 插件管理器最常见的应用是在大型应用程序或框架中,将应用程序或框架分解为多个可插拔的组件。这种模块化的方式允许更容易地维护和定制应用程序,同时提高了应用程序的可扩展性和重用性。 总之,基于Qt插件插件管理器是一种灵活和可扩展的架构,它能够帮助开发人员以模块化的方式构建应用程序和框架,以满足不同的需求和场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值