博客 / 詳情

返回

Qt 插件編程實踐

緣由

最近在用Qt做項目,在網上找插件編寫的資料,沒有完整的代碼,要下載的資源都被傳到需要積分的網站上了,感覺很不爽。因此把插件示例項目編寫完整,並在github上開了一個qtDemo項目,寫了這篇文章。
作為一個拖磚項目,望大家在學習同時,不要忘記了分享的精神。這個項目我會把學習Qt的代碼不斷更新上來,若有同道者,請pull request給我,本項目收集Qt示例程序,謝謝!

技術選擇

我的項目最低支持msvc 10.0 x86,g++ 4.8.5,Qt5.5.1,因為需要跨平台,項目管理使用cmake(最低支持3.10),現在同時支持windows和linux,IDE我使用qcreator,在windows下你也可以選擇visual studio 2019。

Cmake介紹

使用cmake主要是因為它的跨平台性好,可以脱離操作系統與IDE的束縛,而且已經被廣泛支持了。
設置cmake所需最低版本號

cmake_minimum_required(VERSION 3.10)

項目及版本號,項目名稱可以通過${PROJECT_NAME}取得

project(plg1 VERSION 0.1.0)

將當前目錄加入文件包含搜索目錄,若不設置,vs2019與qcreator的當前目錄會不兼容。

set(CMAKE_INCLUDE_CURRENT_DIR ON)

打開全局moc與全局uic

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)

查找系統中已安裝的Qt版本,需要的庫。
最好每一個庫都要寫,Qt也會根據依賴關係自動添加

find_package(Qt5 REQUIRED Widgets)
find_package(Qt5Widgets)
find_package(Qt5Core)
find_package(Qt5Gui)

需要建立名稱為QTDIR的系統環境變量,指定安裝的Qt目錄。

收集我的們源文件,這有很多方法,大家可以去了解並使用自己喜歡的方式。

FILE(GLOB SRC_FILES "*.cpp" "*.h" "*.ui")

創建工程文件

add_executable(${PROJECT_NAME} ${SRC_FILES})               #可執行文件創建方式
add_library(${PROJECT_NAME} SHARED ${SRC_FILES})      #動態鏈接庫創建方式

添加子項目,也就是我們的插件

add_subdirectory(sub1)
add_subdirectory(sub2)

添加Qt5依賴項

target_link_libraries(${PROJECT_NAME} Qt5::Widgets Qt5::Core Qt5::Gui)

Qt5插件

項目結構

本示例項目包括三個工程,一個主工程,兩插件工程。cmake的項目是以目錄為基礎的,每個工程的目錄下會有一個cmakelists.txt工程文件。

插件基類

主工程中的plugindemoplugin.h是所有插件的基類,我們的每個插件都繼承自這個類,它做了插件基礎聲明及我們的插件能做的行為。本示例每一個插件會給主程序返回一個widget作為centerWidget顯示,並提供一個name和information的查詢接口,提供插件必要的信息。

class QtPluginDemoInterface
{
public:
    virtual ~QtPluginDemoInterface() {}
    virtual QString name() = 0;
    virtual QString information() = 0;        
    virtual QWidget *centerWidget() = 0;  //返回一個Widget設置到centerwidget中進行顯示
};
//s聲明接口
#define PluginDemoInterface_iid "com.Interface.MainInterface"
Q_DECLARE_INTERFACE(QtPluginDemoInterface, PluginDemoInterface_iid)

插件定義

我們這隻對sub1進行一下説明,sub2是類似的,請自行閲讀代碼。
子項目的工程文件(cmakelists.txt)與主項目的主要的差別是一個是創建可執行文件,一個是創建動態鏈接庫。
頭文件plugindemo.h :

#include "../plugindemoplugin.h"

class pluginDemo : public QObject, QtPluginDemoInterface
{
    Q_OBJECT                //Qt類的標識宏,初學Qt的小夥伴要注意,這行是Qt類必須的
    Q_INTERFACES(QtPluginDemoInterface)                //這兩行聲明是插件要求的
    Q_PLUGIN_METADATA(IID PluginDemoInterface_iid)

public:
    pluginDemo(){};
    ...        //方法聲明,省略
};

源文件plugindemo.cpp :

QWidget *pluginDemo::centerWidget()
{
    auto btn = new QPushButton("One");    //我們返回一個按鈕,作為簡單的widget示例
    return  btn;
}

... //其它的省略了,只是固定信息返回

主程序中加載插件

mainwindow.h定義:

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    int MainWindow::loadPlugins();
    void MainWindow::populateMenus(QObject * pluginInterface,QtPluginDemoInterface*i );
    void MainWindow::slt_WidgetActionTriggered();

private:
    Ui::MainWindow *ui;
};

通過loadPlugins函數加載插件:

int MainWindow::loadPlugins()
{
    QDir pluginsDir = QDir(QCoreApplication::applicationDirPath());  //這裏要注意路徑需要配置好,把子工程的輸出目錄配置到主工程
    if(!pluginsDir.cd("plugins")) return -1;                                          //下的plugins目錄中
    foreach (QString fileName, pluginsDir.entryList(QDir::Files))
    {
        QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
        QObject *plugin = pluginLoader.instance();
        if(plugin)
        {
            auto centerInterface = qobject_cast<QtPluginDemoInterface*>(plugin);
            if(centerInterface)
            {
                populateMenus(plugin,centerInterface);  //將插件作為菜單中的一項
            }
        }
    }
    return count;
}

生成菜單函數:

void MainWindow::populateMenus(QObject * pluginInterface,QtPluginDemoInterface*i )
{
    static auto menu = menuBar()->addMenu("widgets");  //建立一個菜單項
    auto act  = new QAction(i->name(),pluginInterface);    //建立action,取得的插件對象被綁定在其上
    connect(act,&QAction::triggered,this,&MainWindow::slt_WidgetActionTriggered);  //事件鏈接
    menu->addAction(act);
}

菜單點擊事件響應:

void MainWindow::slt_WidgetActionTriggered()
{
    QtPluginDemoInterface * plg = qobject_cast<QtPluginDemoInterface*>(sender()->parent());    //取得插件對象
    auto centerWidget = plg->centerWidget();        //取得插件中返回的widget
    //我們返回的widget其實是QPushButton,用其配置信息為其設置顯示內容
    (qobject_cast<QPushButton*>(centerWidget))->setText(plg->information());    
    setCentralWidget(centerWidget);
}

項目地址

https://github.com/zhoutk/qtDemo

命令行編譯

git clone https://github.com/zhoutk/qtDemo
cd qtDemo/plugin & mkdir build & cd build
cmake ..
cmake --build .      

編譯時注意:cmake默認為x86架構,需要與你安裝的Qt版本對應;編譯好了,運行前,請注意目錄結構是否正確。

小結

拋磚引玉,不吝賜教,謝謝閲讀!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.