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