Qt自定义插件plugin的开发和调用
1.需求描述
设备管理组件保存了设备信息和通道信息到sqlite数据库,其他组件也想要访问这个数据库中的内容;需要开发一个自定义插件,用于提供接口给其他组件访问数据库;
开发环境vs2015+Qt5.9.6
2.插件介绍
插件主要面向接口编程,通过接口实现功能的扩展,而不需要访问.lib文件。插件在程序运行时即使.dll不存在,程序也能正常启动,只是相应插件功能无法正常使用。相比之下,DLL(动态链接库)需要访问.lib文件,并且在程序运行时必须保证.dll存在,否则无法正常启动。插件在调用时只需要.dll文件,不需要头文件和lib文件,这表明插件的设计更加注重于功能的动态添加和热插拔,而DLL则更侧重于提供可重用的代码和数据。
3.自定义插件实现
(1)创建一个Qt Library工程;将工程输出设置为dll;
(2)定义一个纯虚函数接口
#pragma once #ifndef DbPluginInterface_H #define DbPluginInterface_H #include <QVariantMap> #include <QString> class DbPluginInterface { public: virtual ~DbPluginInterface() {} virtual int initLocalDb(QString strDbPath)=0; virtual int finishLocalDb() = 0; /*执行SQL语句*/ virtual int ExcuateSql(QString strSql, QVariantMap& replyData, QString& strMsg)=0; virtual int ExcuateSql(QString strSql, QString& strMsg)=0; }; //声明接口,和接口id。 Q_DECLARE_INTERFACE(DbPluginInterface,"org.qter.Example.myplugin.RexExpInterface") #endif
(3)继承纯虚函数接口实现导出类
#ifndef DBPLUGIN_H #define DBPLUGIN_H #include "dbplugin_global.h" #include <QSqlDatabase> #include <QObject> #include "DbPluginInterface.h" class DbPlugin :public QObject,public DbPluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qter.Example.myplugin.RexExpInterface" FILE "DbPlugin.json") Q_INTERFACES(DbPluginInterface) public: DbPlugin(); ~DbPlugin(); int initLocalDb(QString strDbPath); int finishLocalDb(); /*执行SQL语句*/ int ExcuateSql(QString strSql, QVariantMap& replyData, QString& strMsg); int ExcuateSql(QString strSql, QString& strMsg); private: QSqlDatabase db_; }; #endif // DBPLUGIN_H
要在类定义中加入下面两行宏定义:
Q_PLUGIN_METADATA(IID "org.qter.Example.myplugin.RexExpInterface" FILE "DbPlugin.json")
Q_INTERFACES(DbPluginInterface)
Q_PLUGIN_METADATA介绍
要在类定义中加入下面两行宏定义;Q_PLUGIN_METADATA
宏在 Qt 插件开发中用于声明和提供插件的元数据(metadata),使得 Qt 的插件机制能够识别并正确加载该插件。通常与 Q_OBJECT
和 Q_INTERFACES
一起使用。Q_PLUGIN_METADATA
宏将插件的信息嵌入到生成的共享库中。这个信息包含插件的标识符、版本号、描述等,可供插件加载器 (QPluginLoader
) 在运行时识别和使用。这些信息也可以在插件加载前进行检索,从而允许应用程序根据插件的元数据作出决策。
Q_PLUGIN_METADATA 宏的基本语法如下:
Q_PLUGIN_METADATA(IID "插件接口标识符" FILE "元数据文件.json")
IID: 插件接口标识符,用于唯一标识插件接口。通常是一个字符串,与 Q_DECLARE_INTERFACE 中声明的标识符相对应。
FILE: 可选参数,用于指定一个包含插件元数据的 JSON 文件,内容如下。DbPlugin.json文件放到工程路径(dbplugin.cpp同目录下)下,否则编译时会报错Plugin Metadata file ".json" does not exist.
{ "name": "MyPlugin", "version": "1.0", "description": "This is a sample plugin for demonstration purposes.", "author": "Your Name", "license": "GPL" }
调用者可以通过QPluginLoader
的 metaData().metaData().toVariantMap().value("MetaData")
方法获取到这个元数据,可以通过元数据的内容了解插件的信息;
QDir pluginsDir("../x64/Release/"); QPluginLoader loader(pluginsDir.absoluteFilePath("DbPlugin.dll")); QVariantMap metadata=loader.metaData().toVariantMap();
Q_INTERFACES宏介绍
在实现插件时使用,用于指定插件实现了哪些接口,从而使运行时的Qt插件系统知晓该插件提供了哪些功能接口,并根据这些接口来调用插件的功能;与Q_DECLARE_INTERFACE宏定义配套使用,Q_DECLARE_INTERFACE在申明插件接口时使用:用于给插件接口类(ClassName)绑定一个唯一标识符(Identifier)。
(4)实现插件导出类dbpugin.cpp
#include "dbplugin.h" #include <QCoreApplication> #include <QFile> #include <QSqlQuery> #include <QSqlError> #include <QSqlRecord> #include<QDir> DbPlugin::DbPlugin() { } DbPlugin::~DbPlugin() { } int DbPlugin::initLocalDb(QString strDbPath) { int iret = -1; do { QString strPath = QDir::currentPath(); db_ = QSqlDatabase::addDatabase("QSQLITE"); QFile file(strDbPath); if (!file.exists()) { break; } db_.setDatabaseName(strDbPath); if (!db_.open()) { break; } iret = 0; } while (0); return iret; } int DbPlugin::finishLocalDb() { db_.close(); return 0; } int DbPlugin::ExcuateSql(QString strSql, QVariantMap& replyData, QString& strMsg) { int errorCode = -1; QSqlQuery query; query.prepare(strSql); if (!query.exec()) { QSqlError error = query.lastError(); errorCode = error.type(); strMsg = error.text(); //LOG_ERROR("ExcuateSql %s failed,code:%d,errormsg:%s", strSql.toStdString().c_str(), errorCode, strMsg.toStdString().c_str()); return -1; } QVariantList dataList; while (query.next()) { QSqlRecord record = query.record(); int column = record.count(); QVariantMap data; for (int i = 0; i < column; i++) { data.insert(record.fieldName(i), record.value(i)); } dataList.append(data); } //QVariantMap replyData; replyData.insert("totalCount", dataList.size()); replyData.insert("data", dataList); return 0; } int DbPlugin::ExcuateSql(QString strSql, QString& strMsg) { int errorCode = -1; QSqlQuery query; query.prepare(strSql); if (!query.exec()) { QSqlError error = query.lastError(); errorCode = error.type(); strMsg = error.text(); //LOG_ERROR("ExcuateSql %s failed,code:%d,errormsg:%s", strSql.toStdString().c_str(), errorCode, strMsg.toStdString().c_str()); return -1; } return 0; }
要实现数据库的加载和访问,需要配置sql模块,增加sql驱动;这样才能正确访问数据库;
4.调用工程如何使用插件
(1)创建一个工程,将接口文件DbPluginInterface.h复制到工程,创建数据库调用单例类,
#ifndef DBPLUGIN_H #define DBPLUGIN_H #include "base_define.h" #include <QObject> #include "DbPluginInterface.h" class DbPlugin : public QObject,public singleton<DbPlugin> { Q_OBJECT public: DbPlugin(); ~DbPlugin(); int LoadDbPlugin();//加载插件 /*执行SQL语句*/ int ExcuateSql(QString strSql, QVariantMap& replyData, QString& strMsg); int ExcuateSql(QString strSql, QString& strMsg); private: DbPluginInterface* pDbpluginInstance=NULL; }; #endif // DBPLUGIN_H
(2)实现插件加载和接口封装,然后就可以再这个工程中调用单例类的接口,通过插件访问数据库
#include "DbPlugin.h" #include <QPluginLoader> #include <QDir> DbPlugin::DbPlugin() { } DbPlugin::~DbPlugin() { } int DbPlugin::LoadDbPlugin() { QDir pluginsDir("../x64/Release/");//指定插件的相对路径 QPluginLoader loader(pluginsDir.absoluteFilePath("DbPlugin.dll"));///通过相对路径获取插件的绝对路径 QObject *plugin = loader.instance(); if (plugin) { pDbpluginInstance = qobject_cast<DbPluginInterface *>(plugin); if (pDbpluginInstance==NULL) { return -1; } } QString strDbPath = pluginsDir.absoluteFilePath("localSqliteDb.db");//sqlite和插件在同一路径,如果是其他路径,要传入数据库文件的路径,否则会报错; if (pDbpluginInstance->initLocalDb(strDbPath)!=0) { return -1; } return 0; } int DbPlugin::ExcuateSql(QString strSql, QVariantMap& replyData, QString& strMsg) { if (pDbpluginInstance==NULL) { return-1; } return pDbpluginInstance->ExcuateSql(strSql, replyData, strMsg); } int DbPlugin::ExcuateSql(QString strSql, QString& strMsg) { if (pDbpluginInstance == NULL) { return -1; } return pDbpluginInstance->ExcuateSql(strSql, strMsg); }