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

kevinlq發表於2021-11-20

閱讀本文大概需要 6 分鐘

在上一篇大概瞭解了關於Qt Creator 基礎知識後[1],本篇先學習下框架基本結構,這樣能夠清晰的知道這個框架當中包含哪些檔案、資料夾、工程檔案,這些檔案分別代表什麼意思以及有什麼作用

檔案結構

開啟下載好的原始碼,如下目錄所示

可以看出來,檔案和資料夾很多,不要被這些表面嚇著,我們真正需要關心的沒有幾個,需要重點關注的我加粗顯示了

  • bin資料夾
  • dist 資料夾
  • doc 資料夾
  • qbs 資料夾
  • scripts 資料夾
  • share 資料夾
  • src 資料夾
  • tests 資料夾
  • docs.pri
  • qtcreator.pri
  • qtcreator.pro
  • qtcreator.qbs
  • qtcreatordata.pri
  • README.md

這裡我們主要要關注src資料夾,這個下面是這個框架的原始碼,其它的資料夾先不看

qtcreator.pri檔案是專案工程中的一些通用配置,比如版本號,一些庫的輸出路徑定義,每個外掛或者子工程都會包含該配置檔案,方便直接配置工程一些變數(具體怎麼配置,後面會講解到)

qtcreator.pro檔案是主工程檔案,要開啟編譯原始碼也是需要開啟該工程檔案進行載入的

PS: 涉及到 qbs 相關內容可以不用關注了,Qt Build Suite 也是一種跨平臺的編譯工具,目前使用較少無需關注

框架結構

下面來詳細看下工程結構是如何管理的,以及整個框架原理

使用 Qt Creator 開啟工程後你會發現有很多子工程專案,這個時候不要亂、不要怕,我們目前只需要關心三個部分就可以了

  • libs
  • plugins
  • app

libs部分

libs工程下面包含了常用的一些通用方法,我們目前關注三個即可

Aggregation工程

這個類提供了「打包」功能,可以將很多元件打包成一個整體,整個理解起來有點抽象,你可以理解為將多個物件封裝成一個物件,這個物件對外提供了所有物件的介面屬性和方法

  • Aggregation 集合內部每個元件物件都可以互相轉化
  • Aggregation 集合內每個物件的生命週期被繫結在了一起,即一個在全部在,一個被刪除析構那麼其餘的元件也就會被析構

extensionsystem工程

這個類實現了外掛的管理功能,是整個框架的核心部分,所有的外掛生命週期管理都在這個類裡面實現

  • IPlugin 外掛基類,後面所有的外掛都是繼承自它來實現所有功能的,有三個重點方法需要關注
    virtual bool initialize(const QStringList &arguments, QString *errorString) = 0;
    virtual void extensionsInitialized() = 0;
    virtual bool delayedInitialize() { return false; }

外掛的初始化,外部依賴初始化,延遲初始化,這三個虛擬函式用來初始化每個外掛各自的一些資源資訊。外部依賴那個也尤為重要,比如我們某個外掛同時依賴多個其它外掛,那麼就需要在這裡處理等待其它外掛載入完成才算完成

  • PluginManager 外掛管理單例類,整個框架只有一份,負責框架外掛的管理,隨著程式退出它的宣告週期才結束
  • PluginManagerPrivate 外掛管理具體實現邏輯類,看名字就很清楚,典型的P-D指標關係,這樣是為了把外掛系統擴充套件的具體實現隱藏不給外部暴露,這種技巧在後面很多程式碼中經常會見到,也是值的我們去學習
  • PluginSpec 外掛核心類,該類實現外掛的所有屬性
class EXTENSIONSYSTEM_EXPORT PluginSpec
{
public:
    enum State { Invalid, Read, Resolved, Loaded, Initialized, Running, Stopped, Deleted};

    ~PluginSpec();
    
    // 外掛名字
    QString name() const;
    // 外掛版本
    QString version() const;
    // 外掛相容版本
    QString compatVersion() const;

    // 外掛提供者
    QString vendor() const;

    // 外掛版權
    QString copyright() const;

    // 外掛協議
    QString license() const;

    // 外掛描述
    QString description() const;

    // 外掛主頁 URL
    QString url() const;
    
    // 外掛類別,用於在介面分組顯示外掛資訊
    QString category() const;
}

每個外掛(每個動態庫)都有一份該物件,用來記錄該外掛的所有屬性資訊,這些屬性資訊是通過 json配置檔案讀入的,這些資訊被稱為外掛的「元資訊」,後面關注外掛實現會提到

utils 工程

這個工程裡面封裝了一些基礎功能演算法類,比如檔案操作、資料排序操作、json互動操作、字串操作集合等,還有一些基礎封裝控制元件實現也在這個裡面

比如後面要提到的核心外掛主視窗QMainWindow類,基類就在在這裡

class QTCREATOR_UTILS_EXPORT AppMainWindow : public QMainWindow
{
    Q_OBJECT
public:
    AppMainWindow();

public slots:
    void raiseWindow();

signals:
    void deviceChange();

#ifdef Q_OS_WIN
protected:
    virtual bool winEvent(MSG *message, long *result);
    virtual bool event(QEvent *event);
#endif

private:
    const int m_deviceEventId;
};

這裡主要是一些事件變化後通知外部處理,比如這裡如果主題發生改變傳送對應訊號出去,裝置發生改變(插拔光碟機等)發出一個裝置改變事件到 Qt 事件佇列去處理

plugin 部分

這部分是每個外掛實現部分,重點需要關注核心外掛corePlugin的實現,其它外掛都是要依賴核心外掛來實現業務功能

在這個外掛裡面主要初始化了主視窗、選單管理類例項以及一些模式管理物件初始化

後面我們會看到各種各樣的外掛,比如你開啟Qt Creator的時候首頁顯示的內容,也是單獨的一個外掛,名字叫做weilcome

每個外掛都有一個標識ID,用來區分是你自己寫的外掛,防止別人惡意修改外掛

Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Core.json")
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Welcome.json")

每個外掛還有一個對應的後設資料描述配置檔案,這個檔案配置了該外掛的一些基本資訊,比如外掛名字、版本、所有權、依賴那些外掛等,這些配置資訊在編譯時會寫進該外掛動態庫當中,採用的是Qt的元物件技術來實現的,這樣在外掛載入執行時就能通過反射動態獲取這些資訊,繼而用來進行一些外掛之間載入關係的驗證

一個簡單的配置描述如下所示

{
    \"Name\" : \"Welcome\",
    \"Version\" : \"$$QTCREATOR_VERSION\",
    \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
    \"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\" : \"Secondary Welcome Screen Plugin.\",
    \"Url\" : \"http://www.qt.io\",
    $$dependencyList
}

其中很關鍵的是一些變數值,比如$$QTCREATOR_VERSION,通過這個變數直接可以讀取到我們在工程qtcreator.pri中定義的變數值,繼而快速統一載入顯示,其它變數值獲取類似

其次,需要關注的是每個外掛的配置依賴檔案比如welcome_dependencies.pri,該檔案中包含了依賴那些庫那些外掛

# 外掛名字
QTC_PLUGIN_NAME = Welcome

# 外掛依賴的庫
QTC_LIB_DEPENDS += \
    extensionsystem \
    utils
    
# 外掛依賴的外掛
QTC_PLUGIN_DEPENDS += \
    coreplugin

某個外掛依賴那些外掛和動態庫,只需要在對應位置追加其名字即可,工程配置檔案會自動進行載入,這樣編寫可以減少很多重複工作,而且外掛依賴關係也很清楚的看到

app 部分

這個部分是程式入口實現部分,這裡主要是獲取外掛路徑,初始化外掛、配置檔案,載入每個外掛,如果都沒有錯誤,那麼初始化完成後主介面就會顯示出來,直接看主函式入口看就行

關鍵部分是外掛管理器的初始化,設定外掛搜尋路徑後對每個外掛進行初始化操作

    PluginManager pluginManager;
    PluginManager::setPluginIID(QLatin1String("org.qt-project.Qt.QtCreatorPlugin"));
    PluginManager::setGlobalSettings(globalSettings);
    PluginManager::setSettings(settings);
    ......
    const QStringList pluginPaths = getPluginPaths() + customPluginPaths;
    PluginManager::setPluginPaths(pluginPaths);
    
    ......
    PluginManager::loadPlugins();

在這裡還有一個需要注意的地方,就是這個檔案app_version.h.in

這個是一個模板檔案,qmake載入執行完畢後,會在臨時目錄下生成對應的標頭檔案app_version.h

#pragma once

namespace Core {
namespace Constants {

#define STRINGIFY_INTERNAL(x) #x
#define STRINGIFY(x) STRINGIFY_INTERNAL(x)

const char IDE_DISPLAY_NAME[] = \"$${IDE_DISPLAY_NAME}\";
const char IDE_ID[] = \"$${IDE_ID}\";
const char IDE_CASED_ID[] = \"$${IDE_CASED_ID}\";

#define IDE_VERSION $${QTCREATOR_VERSION}
#define IDE_VERSION_STR STRINGIFY(IDE_VERSION)
#define IDE_VERSION_DISPLAY_DEF $${QTCREATOR_DISPLAY_VERSION}

#define IDE_VERSION_MAJOR $$replace(QTCREATOR_VERSION, "^(\\d+)\\.\\d+\\.\\d+(-.*)?$", \\1)
#define IDE_VERSION_MINOR $$replace(QTCREATOR_VERSION, "^\\d+\\.(\\d+)\\.\\d+(-.*)?$", \\1)
#define IDE_VERSION_RELEASE $$replace(QTCREATOR_VERSION, "^\\d+\\.\\d+\\.(\\d+)(-.*)?$", \\1)

const char * const IDE_VERSION_LONG      = IDE_VERSION_STR;
const char * const IDE_VERSION_DISPLAY   = STRINGIFY(IDE_VERSION_DISPLAY_DEF);
const char * const IDE_AUTHOR            = \"The Qt Company Ltd\";
const char * const IDE_YEAR              = \"$${QTCREATOR_COPYRIGHT_YEAR}\";

#ifdef IDE_REVISION
const char * const IDE_REVISION_STR      = STRINGIFY(IDE_REVISION);
#else
const char * const IDE_REVISION_STR      = \"\";
#endif
...

} // Constants
} // Core

這個模板檔案定義了一些常量,某些變數值引用的是巨集定義,最後編譯後巨集定義會被替換掉真正的值,在我們程式碼中引入時真正起作用,更加詳細使用過程後面統一分析pro檔案技巧時會提到

總結

學習到這裡,已經大概清楚了Qt Creator框架的基本結構了,首先是一些基本庫,這些動態庫封裝了一些基本功能和用法,方便在多個模組重複呼叫使用,其次是外掛管理系統的實現,主要包含外掛物件宣告週期管理,外掛載入、外掛解除安裝、外掛直接依賴關係處理

比如有外掛A、B、C,C外掛現在同時依賴於外掛A和B,那麼在載入時就需要特殊考慮

最後就是多個外掛的初始化,主視窗和選單元件管理類,方便擴充到其它外掛進行訪問管理

整個QTC外掛系統是由一個個動態庫構成的,每個外掛互相配合實現了這樣一個複雜的跨平臺的IDE,仔細研究下就可以發現很多奇妙的用法和知識

相關閱讀

  • Qt Creator 學習筆記01,初識 QTC[1:1]

  1. http://kevinlq.com/2021/11/01/learn01_studyQTC/ ↩︎ ↩︎

相關文章