Qt Creator 原始碼學習筆記04,多外掛實現原理分析

kevinlq發表於2021-12-22

閱讀本文大概需要 8 分鐘

外掛聽上去很高大上,實際上就是一個個動態庫,動態庫在不同平臺下字尾名不一樣,比如在 Windows下以.dll結尾,Linux 下以.so結尾

開發外掛其實就是開發一個動態庫,該動態庫能夠很好的載入進主程式、訪問主程式資源、和主程式之間進行通訊

本篇文章一起學習下 Qt Creator當中是如何實現外掛的開發、載入、解析、管理以及互相通訊的,方便我們開發自定義外掛打下基礎

簡介

Qt Creator 外掛理解起來其實很簡單,定義一個介面類作為基類,其他外掛需要繼承該類實現對應的虛方法,每個外掛作為獨立子工程編譯後生成對應的動態庫

主函式載入每個外掛物件,然後轉化為對應外掛例項

QPluginLoader loader(pluginName);
loader.load();
IPlugin *pluginObject = qobject_cast<IPlugin*>(loader.instance());

// 比如轉為核心外掛例項
CorePlugin *pCorePluginObj = qobject_cast<CorePlugin*>(loader.instance());

然後每個外掛各自根據對應業務邏輯呼叫介面就行了

當然了,Qt Creator 在實現過程當中肯定不止這麼簡單,外掛的載入、解析、解除安裝等管理還是比較複雜的,非常值得我們去學習

外掛組成

整個外掛系統由外掛管理器、核心外掛、其它外掛組成,其中核心外掛是系統中不可缺少的,其它外掛都要依賴核心外掛來進行開發通訊

我們先開啟 Qt Creator 外掛選單看看都包含那些外掛

可以看到所有的外掛根據型別進行了分組,同一個型別外掛同屬一個樹節點,每個外掛後面有個核取方塊可以控制載入/解除安裝該外掛

每個外掛還包含了版本資訊以及歸屬作者資訊,這些資訊都可以通過物件後設資料來配置,外掛的版本也很有用,我們編寫的外掛可以限定在某個版本之間相容,這個時候版本號就起作用了,詳細實現後面會講解到

我們可以載入、解除安裝某個外掛,但是無論怎麼選擇,核心Core外掛是不能解除安裝的,why? 因為整個外掛系統是建立在 Core 核心外掛基礎之上的,離開核心外掛其它外掛無法存活

所以我們學習的重點應該放在核心外掛上,學會後其它外掛很容易上手了

外掛管理

外掛的核心其實就是對外掛的管理,這個是本篇的重點,是我們閱讀原始碼時需要重點關注的部分,為什麼這麼說呢,我舉個例子大家就清楚了

我們日常寫程式碼的時候,比如定義一個變數,需要關注的有這麼幾點:

  • 變數的名
  • 變數的值
  • 變數的型別
  • 變數的作用域
  • 變數的生命週期

對每個定義的變數都非常清楚它的一些要素,那麼肯定不會出錯的

外掛也一樣,每個外掛到實際開發當中也是一個個物件,我們定義的物件是什麼型別?名字叫什麼?它的值是多少?它的作用域範圍是什麼?生命週期呢?什麼時候建立和釋放?

搞清楚上述這些,對於理解外掛管理工作就更進一步了,下面重點來看看外掛的生命週期

外掛管理器

外掛管理器實現主要在PluginManager 類當中實現,該類管理了所有的外掛載入、解除安裝以及釋放

物件管理池

class EXTENSIONSYSTEM_EXPORT PluginManager : public QObject
{
    Q_OBJECT
public:
    static PluginManager *instance();
    static void addObject(QObject *obj);
    static void removeObject(QObject *obj);
    
    ......
    
    friend class Internal::PluginManagerPrivate;
}

這個類是一個單例類,主要管理外掛物件,可以理解為物件池,詳細實現都封裝在了 d指標類裡面,

我們繼續進去看看

pluginmanager_p.h

class EXTENSIONSYSTEM_EXPORT PluginManagerPrivate : public QObject
{
    Q_OBJECT
public:
    ......
    QHash<QString, QList<PluginSpec *>> pluginCategories;
    QList<PluginSpec *> pluginSpecs;
    QList<QObject *> allObjects; // ### make this a QList<QPointer<QObject> > > ?
    
    ......
}

可以看到底層儲存每個物件用的容器是 QList,從Qt Creator 4.10版本開始換成了 QVector來儲存,說起來這兩個容器的區別讓我想到了,現在最新版本的 Qt當中,已經把兩者合二為一了

template<typename T> using QVector = QList<T>;

所以使用哪個無所謂了,不過我們還是要搞清楚這兩個容器的區別,什麼時候用Vector,什麼時候用 List

新增物件

void PluginManagerPrivate::addObject(QObject *obj)
{
    {
        QWriteLocker lock(&m_lock);
        if (obj == 0) {
            qWarning() << "PluginManagerPrivate::addObject(): trying to add null object";
            return;
        }
        if (allObjects.contains(obj)) {
            qWarning() << "PluginManagerPrivate::addObject(): trying to add duplicate object";
            return;
        }

        allObjects.append(obj);
    }
    emit q->objectAdded(obj);
}

這塊核心程式碼其實很好理解,每次新增物件前先加鎖,由於使用的是讀寫鎖,不用擔心函式返回死鎖問題,判斷物件是否合法以及是否已經存在,不存在則追加到 list 當中,最後丟擲一個訊號,這個訊號在外部需要使用的地方可以繫結,比如模式切換裡面就使用到了

void ModeManager::init()
{
    QObject::connect(ExtensionSystem::PluginManager::instance(), &ExtensionSystem::PluginManager::objectAdded,
                     m_instance, &ModeManager::objectAdded);
}

新增就對應的刪除,原理和新增一樣

- 刪除物件

void PluginManagerPrivate::removeObject(QObject *obj)
{
    if (obj == 0) {
        qWarning() << "PluginManagerPrivate::removeObject(): trying to remove null object";
        return;
    }

    if (!allObjects.contains(obj)) {
        qWarning() << "PluginManagerPrivate::removeObject(): object not in list:"
            << obj << obj->objectName();
        return;
    }

    emit q->aboutToRemoveObject(obj);
    QWriteLocker lock(&m_lock);
    allObjects.removeAll(obj);
}

同樣的把物件從list 當中進行了刪除,在刪除之前也向外丟擲了訊號,用法和新增訊號配對使用

這裡有個疑問,為啥鎖不在函式最開頭加呢?

外掛管理

每個外掛物件對應到底層是由 PluginSpec 來例項化的,每個外掛使用 list容器儲存,如下所示

QList<PluginSpec *> pluginSpecs;

外掛核心類實現

class EXTENSIONSYSTEM_EXPORT PluginSpec
{
public:
    QString name() const;
    QString version() const;
    QString compatVersion() const;
    QString vendor() const;
    QString copyright() const;
    ......
    bool isRequir   ed() const;
    
    ......
    QVector<PluginDependency> dependencies() const;
    
private:
    PluginSpec();
}

閱讀程式碼就可以發現,這個類主要是記錄了每個外掛的一些基本資訊,那麼這些資訊是如何賦值的呢?通過外掛描述檔案來進行自動載入的,後面學習核心外掛會看到

有個核心部分程式碼,外掛依賴項dependencies,這個主要解決外掛之間依賴關係使用,這個類也很簡單很好理解

/*
 * 外掛依賴相關資訊
*/
struct EXTENSIONSYSTEM_EXPORT PluginDependency
{
    enum Type {
        Required,       // 必須有此依賴
        Optional,       // 此依賴不是必須的
        Test
    };

    PluginDependency() : type(Required) {}

    QString name;           //被依賴的外掛名字
    QString version;        //對應的版本號
    Type type;              //依賴型別
    bool operator==(const PluginDependency &other) const;
    QString toString() const;
};

比如外掛A依賴外掛BC,那麼在外掛A載入的時候對應的list當中就包含了B,C外掛資訊,必須等到這兩個外掛載入完成後才能載入外掛A,這一點很重要

外掛載入流程

前面學習了外掛管理器當中的一些基本資料結構,現在來看看這些外掛是怎麼載入進去的,載入順序和流程是怎麼樣的

外掛載入流程比較複雜一些,同時也是最重要的部分,主要分為下面幾個步驟

外掛載入流程

下面我們來詳細看看每個步驟都幹了那些工作,原始碼面前了無祕密

設定外掛 IID

setPluginIID(const QString &iid)

這個id 是全域性唯一,載入外掛時會首先判斷外掛 ID 合法性,用於確定是你自己編寫的外掛,這樣可以防止其它外掛惡意註冊載入

大家可以想想一下,如果別人也寫了一個類似的外掛,那麼如果沒有 ID 區分是不是就能載入進外掛系統當中,從而破壞軟體結構?

Qt Creator 預設的 ID 為 org.qt-project.Qt.QtCreatorPlugin,每個外掛載入時通過巨集進行設定

class CorePlugin : public ExtensionSystem::IPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Core.json")
}

巨集展開後:

#define Q_PLUGIN_METADATA(x) QT_ANNOTATE_CLASS(qt_plugin_metadata, x)

#define QT_ANNOTATE_CLASS(type, ...)

這個巨集是為了配合moc處理器生成外掛匯出函式,最終在呼叫外掛介面返回例項時能夠準確返回自己。我們寫個 demo 來驗證下

新建一個外掛叫做 PluginDemo

class PluginDemo : public ExtensionSystem::IPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "PluginDemo.json")
};

qmake 編譯一下看下中間結果內容:

static const qt_meta_stringdata_PluginDemo_t qt_meta_stringdata_PluginDemo = {
    {
QT_MOC_LITERAL(0, 0, 10) // "PluginDemo"

    },
    "PluginDemo"
};
void *PluginDemo::qt_metacast(const char *_clname)
{
    if (!_clname) return Q_NULLPTR;
    if (!strcmp(_clname, qt_meta_stringdata_PluginDemo.stringdata0))
        return static_cast<void*>(const_cast< PluginDemo*>(this));
    return ExtensionSystem::IPlugin::qt_metacast(_clname);
}

設定全域性配置類

setGlobalSettings(QSettings *settings)

全域性配置,一般存放的是預設值,用於恢復設定使用

設定區域性配置類

setSettings(QSettings *settings)

存放程式當前配置引數類。比如我們設定某個引數配置儲存後會存在某個配置檔案中,程式載入時會從該檔案載入到QSettings物件當中供我們呼叫

設定外掛路徑

setPluginPaths(const QStringList &paths)

外掛路徑一般是我們 exe 程式相鄰路徑下的,比如plugins/xxx.dll,當然也可以為任意路徑下的動態庫,只要路徑正確合法都可以載入的,可以設定多條外掛路徑

比如正常 Qt Creator啟動時會給兩個路徑分別為:

 ("D:/CloundCode/QTC/bin/Win32/Debug/QTC/lib/qtc/plugins",
 "C:/Users/devstone/AppData/Local/QtProject/qtc/plugins/4.4.1")

關於路徑的獲取可以看後面主程式載入部分可以看到

讀取外掛資訊

用於讀取外掛原物件資訊,主要包含三個過程

readMetaData()
resolveDependencies()
pluginsChanged()
  • 讀後設資料:這裡會挨個讀取每個外掛,初始化 QPluginLoader,設定名字,為後面載入做準備,可以叫預載入,建立外掛例項物件 PluginSpec,儲存到 List 結構當中
  • 檢測依賴關係::用於重新載入分析每個外掛依賴關係,是一個雙重迴圈,每個外掛會和其它外掛比較一次,最後按照外掛名字進行排序
  • 外掛改變:向外丟擲訊號,外掛管理視窗用來重新整理 view 列表資訊

載入外掛

到了這裡才開始真正載入外掛了,主要包括下面幾個流程

loadQueue()
loadPlugins()
(PluginSpec::Loaded)
(PluginSpec::Initialized)
(PluginSpec::Running)
  • 依賴初始化
  • 載入外掛:這裡裡面才會真真去載入初始化每個外掛,計算獲取外掛載入佇列
  • 載入(PluginSpec::Loaded):
loadPlugin(PluginSpec *spec, PluginSpec::State destState)

呼叫 QPluginLoader.load(),真正載入外掛,載入成功才可以獲取每個外掛方法,儲存外掛例項:

IPlugin *pluginObject = qobject_cast<IPlugin*>(loader.instance());
  • 初始化(PluginSpec::Initialized)
loadPlugin(PluginSpec *spec, PluginSpec::State destState)

這裡會呼叫每個外掛的初始化函式:initialize(),該函式是純虛擬函式,每個外掛必須重新實現

  • 執行(PluginSpec::Running)
loadPlugin(PluginSpec *spec, PluginSpec::State destState)

呼叫每個外掛擴充套件初始化函式:extensionsInitialized(),此時會挨個判斷買個外掛狀態是否在執行,是的話加入到延遲佇列

  • 延遲初始化
nextDelayedInitialize()

從延遲佇列當中取出買個外掛,呼叫各自延遲初始化函式:delayedInitialize()

外掛載入結束

到此整個外掛載入結束了,可以看出來,整個外掛的載入過程說白了就是動態庫載入解析然後呼叫每個動態庫裡面的虛擬函式來實現的,所有的外掛都繼承自共同的基類(介面),原理很簡單,但是要管理這些外掛尤其是多種依賴關係並存情況下是非常不容易的

看到這裡大家是不是很好奇,為啥不引用標頭檔案直接可以呼叫動態庫裡面的方法了?這個主要使用 QPluginLoader 來實現動態載入動態庫,這個類很好理解,詳細使用可以看我之前寫的SDK呼叫相關文章

包含了使用示例以及對應解析

template <typename T>
T getFunction(QLibrary *lib, const char *symbol)
{
    T f = (T)lib->resolve(func);
    if (f == nullptr)
    {
        return nullptr;
    }
    return f;
}

如何使用 QLibrary 載入動態庫

核心外掛

學習瞭解清楚了外掛如何管理,如何載入,下面來看看核心外掛如何實現,以及如何實現自己的外掛

外掛描述檔案

外掛描述檔案一般用於記錄每個外掛的基本資訊,必須有,而且欄位和用法都是固定的。名字一般取外掛名字,結尾一般都是.json.in

看到這裡是不是好奇,我記得自己第一次看到時也很好奇,為啥是.in結尾,這個其實是一個模板檔案,經過qmake構建後最終在臨時目錄下會生成最終的去掉.in的檔案

Core.json.in

外掛程式碼中包含該檔案

class CorePlugin : public ExtensionSystem::IPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Core.json")
};

檔案內容大概如下所示:

{
    \"Name\" : \"Core\",
    \"Version\" : \"$$QTCREATOR_VERSION\",
    \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
    \"Required\" : true,
    \"HiddenByDefault\" : true,
    \"Vendor\" : \"The Qt Company Ltd\",
    \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
    \"License\" : [ \"Commercial Usage\",
                  \"\",
                  \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\",
                  \"\",
                  \"GNU General Public License Usage\",
                  \"\",
                  \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\"
    ],
    \"Category\" : \"Qt Creator\",
    \"Description\" : \"The core plugin for the Qt IDE.\",
    \"Url\" : \"http://www.qt.io\",
    \"Arguments\" : [
        {
            \"Name\" : \"-color\",
            \"Parameter\" : \"color\",
            \"Description\" : \"Override selected UI color\"
        },
        {
            \"Name\" : \"-theme\",
            \"Parameter\" : \"default|dark\",
            \"Description\" : \"Choose a built-in theme or pass a .creatortheme file\"
        },
        {
            \"Name\" : \"-presentationMode\",
            \"Description\" : \"Enable presentation mode with pop-ups for key combos\"
        }
    ],
    $$dependencyList
}

其實就是一個標準的json配置檔案,每個欄位都很清楚,可能有些變數值不清楚,這裡一起學習下。比如版本號欄位:

\"Version\" : \"$$QTCREATOR_VERSION\",

很明顯後面是一個變數值,也可以說是巨集定義,我們一般定義json配置都是固定值,這裡採用動態配置方法,其中QTCREATOR_VERSION 變數是在pro工程中定義的

這樣做有什麼好處呢?想一想

是不是我們下次變更版本號的時候,直接在pro檔案中更改一次,其它引用到該變數的地方都自動同步了,是不是很方便而且可以減少很多出錯(這就是軟體開發當中的封裝思想)

其實,除過在配置檔案中可以引用變數以外,在程式碼中也可以直接引用,關於如何使用,可以看我之前寫的一篇文章,詳細介紹了原理以及實現方法

qmake奇淫技巧之字串巨集定義

核心外掛初始化

核心外掛主要初始化基本介面結構,包含一個QMainWindow、選單欄、狀態列、模式工具欄、多視窗皮膚等等

正如第一篇筆記當中寫到,如果只編譯核心外掛,那麼執行後整個介面張這個樣子

可以看到僅僅包含基本選單,外掛檢視,狀態列等內容

每個外掛都需要實現自己的初始化函式

bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
{
    ......
}

初始化函式當中首先要註冊所有外掛的mime type型別,這個是從外掛後設資料當中讀取的,會跳過已經關閉的外掛

接著初始化系統主題,主題其實和我們經常用的 qss 樣式表類似,大概張這個樣子

[General]
ThemeName=Flat Dark
PreferredStyles=Fusion
DefaultTextEditorColorScheme=creator-dark.xml

[Palette]
shadowBackground=ff404244
text=ffd0d0d0
textDisabled=60a4a6a8
toolBarItem=b6fbfdff

其實就是一個.ini檔案格式的內容,定義了很多介面樣式相關變數欄位,這些欄位會一一對映到對應主題管理類當中,這樣相關介面設定樣式就可以直接呼叫了

接著也是一個很重要的模組,初始化選單管理類,這個類管理了選單欄所有的選單/Action,以供其它外掛模組訪問

    new ActionManager(this);
    ActionManager::setPresentationModeEnabled(args.presentationMode);

ActionManager 這個類是一個特殊的單例類,單例物件初始化只能在核心外掛當中,雖然提供了單例返回介面,但是首次如果沒有初始化物件返回的是空指標

class CORE_EXPORT ActionManager : public QObject
{
    Q_OBJECT
public:
    static ActionManager *instance();
private:
    ActionManager(QObject *parent = 0);
    ~ActionManager();

    friend class Core::Internal::CorePlugin; // initialization
    friend class Core::Internal::MainWindow; // saving settings and setting context
};

static ActionManager *m_instance = 0;
ActionManager *ActionManager::instance()
{
    return m_instance;
}

所有才有了後面兩個友元類的宣告瞭,這樣可以直接訪問並且初始化物件例項了,核心外掛初始化完成後,其它地方可以直接呼叫單例函式了

接著就是主介面初始化,初始化 mainWindow例項

    m_mainWindow = new MainWindow;
    if (args.overrideColor.isValid())
        m_mainWindow->setOverrideColor(args.overrideColor);
    qsrand(QDateTime::currentDateTime().toTime_t());
    const bool success = m_mainWindow->init(errorMessage);

主介面例項初始化後,接著會呼叫主介面的初始化函式,主介面真正初始化了多外掛介面實現,如果想要學習多外掛介面是如何實現的,可以重點關注下這個初始化函式

最後是編輯模式、查詢相關功能初始化,這些功能不是本次重點,後面有需要再詳細看實現思想

主介面初始化

主介面和我們平時建立專案使用的QMainWindow沒有兩樣,最大的區別就是Qt Creator把介面上所有的操作都進行了封裝管理,這樣其它外掛也可以進行訪問,更好的對介面系統進行了擴充套件

主介面我們重點來學習了選單欄的使用,看看是如何封裝管理的

主要涉及到下面幾個類

  • ActionContainer
  • ActionContainerPrivate
  • MenuActionContainer
  • MenuBarActionContainer
  • ActionManager

這些類的關係如下所示

其中 ActionContainer物件是基類,向外部所有外掛暴露,後面訪問某個選單大部分場景是返回該類指標的

MenuActionContainer 是選單欄當中的選單物件,可以包含 n 個選單

MenuBarActionContainer 是我們的選單欄,整個 MainWindows僅此一份例項

最後就是 ActionManager類了,我們所有的操作均是通過該類來進行,很顯然它是一個單例類,而且整個系統都是可以訪問的

  • 建立選單欄
ActionContainer *menubar = ActionManager::createMenuBar(Constants::MENU_BAR);

if (!HostOsInfo::isMacHost()) // System menu bar on Mac
    setMenuBar(menubar->menuBar());
  • 建立檔案選單
    ActionContainer *filemenu = ActionManager::createMenu(Constants::M_FILE);
    menubar->addMenu(filemenu, Constants::G_FILE);
    filemenu->menu()->setTitle(tr("&File"));

可以看到使用是來是非常方便的,而且這種通過傳入字串建立選單的方式也簡單理解,外部使用的人員完全不用瞭解函式內部是怎麼實現的,只需要根據自己需要傳入規定格式的字串即可

每個選單都有唯一的字串 ID來進行區分,字串命名嚴格按照選單格式,比如

const char M_FILE[]                = "QtCreator.Menu.File";
const char M_FILE_RECENTFILES[]    = "QtCreator.Menu.File.RecentFiles";
const char M_EDIT[]                = "QtCreator.Menu.Edit";

這樣的格式也很好理解,Menu相當於是大選單,後面一級是每個子選單,如果該選單還有子選單,那麼繼續擴充套件下去

其它介面選單欄選單建立和上面的過程是類似的,可以照貓畫虎寫出來

  • 建立每個 Action

上面建立了介面的選單欄,但是每個選單下面還是空的,需要建立對應的 Action 才行,下面來看看是怎麼建立的

    ActionContainer *mfile = ActionManager::actionContainer(Constants::M_FILE);
    
    mfile->addSeparator(Constants::G_FILE_SAVE);

    // Open Action
    QIcon icon = QIcon::fromTheme(QLatin1String("document-open"), Utils::Icons::OPENFILE.icon());
    QAction *m_openAction = new QAction(icon, tr("&Open File or Project..."), this);
    Command *cmd = ActionManager::registerAction(m_openAction, Constants::OPEN);
    cmd->setDefaultKeySequence(QKeySequence::Open);
    mfile->addAction(cmd, Constants::G_FILE_OPEN);
    connect(m_openAction, &QAction::triggered, this, &MainWindow::openFile);

第一行程式碼通過選單管理器返回上面建立的「檔案」選單指標,第二行新增了一個分隔符,後面建立了一個Command物件,這個類是對每個QAction進行了封裝,同時支援設定快捷鍵等操作,這樣我們後續的操作就相當於是一個command

這樣我們的選單欄就建立初始化完成了,剩下的就是左側模式工具條以及中央內容區域的建立了

限於篇幅原因,這些內容我們後面在看

App 程式初始化

前面花費了大量篇幅來介紹外掛的管理以及主介面的實現,下面我們來看看主程式是如何初始化的

主函式 main.cpp 裡面的內容挺多的,我們看主要載入流程就行了

  • 設定系統配置物件指標
    QSettings *settings = userSettings();
    QSettings *globalSettings = new QSettings(QSettings::IniFormat, QSettings::SystemScope,
                                              QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR),
                                              QLatin1String("QTC"));
    PluginManager pluginManager;
    PluginManager::setPluginIID(QLatin1String("org.qt-project.Qt.QtCreatorPlugin"));
    PluginManager::setGlobalSettings(globalSettings);
    PluginManager::setSettings(settings);

主要是系統當中的一些配置,外掛管理器需要記錄那些外掛被禁用了,這樣在後面外掛初始化時可以跳過了

其中很重要的設定外掛ID,這個作為外掛唯一識別符號,用來區分惡意外掛,如果別人不知道你的軟體外掛IID,那麼他編寫的外掛放入你程式目錄下是可以直接通過這個IID過濾掉的

  • 設定外掛路徑
    const QStringList pluginPaths = getPluginPaths() + customPluginPaths;
    PluginManager::setPluginPaths(pluginPaths);

這裡的外掛路徑包含了兩部分,一部分是我們程式目錄下的外掛目錄,另一個是公共目錄,比如下面這個

("D:/CloundCode/QTC/bin/Win32/Debug/QTC/lib/qtc/plugins", "C:/Users/devstone/AppData/Local/QtProject/qtc/plugins/4.4.1")

這一步走完後,如果沒有錯誤整個外掛都載入完成了

  • 異常判斷

想法是美好的,但是事實總不如願,外掛在載入過程中可能會遇到一些問題導致載入異常,這樣程式就無法正常執行了,需要丟擲錯誤給使用者

    const PluginSpecSet plugins = PluginManager::plugins();
    PluginSpec *coreplugin = 0;
    foreach (PluginSpec *spec, plugins) {
        if (spec->name() == QLatin1String(corePluginNameC)) {
            coreplugin = spec;
            break;
        }
    }
    if (!coreplugin) {
        QString nativePaths = QDir::toNativeSeparators(pluginPaths.join(QLatin1Char(',')));
        const QString reason = QCoreApplication::translate("Application", "Could not find Core plugin in %1").arg(nativePaths);
        displayError(msgCoreLoadFailure(reason));
        return 1;
    }
    if (!coreplugin->isEffectivelyEnabled()) {
        const QString reason = QCoreApplication::translate("Application", "Core plugin is disabled.");
        displayError(msgCoreLoadFailure(reason));
        return 1;
    }
    if (coreplugin->hasError()) {
        displayError(msgCoreLoadFailure(coreplugin->errorString()));
        return 1;
    }

這段程式碼對核外掛載入狀況進行了判斷,如果有錯誤沒有載入完成或者被禁用了,那麼就直接返回了。理論上來說核心外掛是無法被禁用的,但是如果有人惡意修改配置檔案禁用了核心外掛,那麼此時程式會無法正常啟動的

  • 載入外掛

這一步其實是最重要的,上面設定外掛路徑後僅僅是讀取每個外掛物件,此時這些對應都是靜態的,到了這一步才真正動起來

PluginManager::loadPlugins();
if (coreplugin->hasError()) {
    displayError(msgCoreLoadFailure(coreplugin->errorString()));
    return 1;
}

關於外掛載入這個流程最前面外掛管理器當中介紹清楚了,這裡我們直接略過就行了

好了關於外掛載入學習就到這裡了

總結

外掛部分內容還是挺長,初次學習原始碼的朋友可能會感覺到無從下手、一臉茫然,不用擔心,我第一次也是這種感覺,遇到不懂不理解的小標記下,先理解掌握整體設計思想和流程,再慢慢逐個模組攻破

軟體開發也是這個道理,一開始你不可能考慮到所有模組細節,把握整體結構沒有問題,再挨個實現細節部分

Qt Creator非常值得我們多看、多寫的,所謂好事多磨麼,看的多了也就明白了一些道理

我們日常開發過程中遇到的一些問題,可能Qt Creator當中早就實現好了,可以直接拿來使用,比如像奔潰dump管理、日誌管理、網路通訊、特殊控制元件實現等都可以拿來直接用的

希望本次分享的筆記對你有幫助,如果覺得有用不妨關注下,有任何問題可以互相交流學習

推薦閱讀

Qt Creator 原始碼學習筆記01,初識QTC

Qt Creator 原始碼學習筆記02,認識框架結構結構

Qt Creator 原始碼學習筆記03,大型專案如何管理工程

相關文章