閱讀本文大概需要 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]