Qt自定義外掛plugin的開發和呼叫

一字千金發表於2024-09-14

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_OBJECTQ_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"
}

呼叫者可以透過QPluginLoadermetaData().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);
}

相關文章