第一次接触Qt是在14年的时候,当时还正沉迷于破解和逆向之中,虽然接触过GUI的开发,但是接触的是国产的编程语言“易语言”,在制作破解补丁的时候只能用别人现成的补丁制作工具或使用易语言进行开发,但经常会出现被杀毒软件报毒,则有了接触新的GUI的想法,查看网上的很多资料之后发现MFC已经“太老了”,Qt正在”青年期”,便开始学习Qt。期间也将之前利用易语言开发的一些程序转为Qt,以用来熟悉Qt的开发。
现已19年了,在Qt Creaotr下进行开发也有4年了,(之前开发是使用Visual studio + Qt-Vs-Addin),第一次使用Qt Creator进行开发的时候就已经深深爱上它。在17年参加工作的时候,当时领导给我派了一个任务就是对Qt Creator的源码进行裁剪,将Qt Creator中的插件系统抽出来,当时只为快速的完成任务,并没有对插件系统的源码进行研究。在之后的工作中,虽然经常使用插件系统来进行插件开发,但是因为没有对源码进行研究,总有一种把握不了的感觉。引用STL源码剖析之中侯捷老师的一句话“源码之前,了无秘密”,希望借着此次源码学习的机会,充分的掌握插件系统的“秘密”。此源码分析基于Qt Creator3.4.2源码。
整个Qt Creator利用插件管理类主要可以分为如下几步:
1、读取插件目录
2、解析插件依赖关系
3、检查Core插件是否可以使用
4、加载整个插件目录下的插件
在读取整个插件目录之前,我们先来看看插件系统做了些什么操作。见src\app\main.cpp
PluginManager pluginManager;
PluginManager::setPluginIID(QLatin1String("org.qt-project.Qt.QtCreatorPlugin"));
//设置插件IID
PluginManager::setGlobalSettings(globalSettings); //设置全局配置
PluginManager::setSettings(settings); //设置用户配置 ...
// Load
const QStringList pluginPaths = getPluginPaths() + customPluginPaths; //获取插件目录
PluginManager::setPluginPaths(pluginPaths); //设置插件目录
从上述源码中,我们可以看到插件系统在读取整个插件目录之前进行了三步操作,第一步操作是设置IID,在了解IID之前,我们需要先了解一下Qt5之后的插件是如何进行创建的,具体的相关内容可以查看Qt官网提供的一篇文章:
How to Create Qt Plugins:https://doc.qt.io/archives/qt-5.6/plugins-howto.html
在上述的文章中,我们可以看到一个名为Q_PLUGIN_METADATA的宏。这个宏可以为我们设置插件的IID,也就是所谓的接口ID和元数据json文件,并且json文件是可选的。
我们查看Qt Creator中的Core插件的coreplugin.h,目录结构为:src\plugins\coreplugin
class CorePlugin : public ExtensionSystem::IPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Core.json")
public:
CorePlugin();
~CorePlugin();
bool initialize(const QStringList &arguments, QString *errorMessage = 0);
void extensionsInitialized();
bool delayedInitialize();
ShutdownFlag aboutToShutdown();
QObject *remoteCommand(const QStringList & /* options */,
const QString &workingDirectory,
const QStringList &args);
....
};
可以看到CorePlugin中使用了Q_PLUGIN_METADATA来声明IID和元数据,在后续的源码分析中,我们可以再次看到IID在整个插件系统中的作用。
第二步操作是设置全局配置和用户配置,在这个readSettings函数里面,会对全局配置和用户配置是否已经通过setGlobalSettings和setSettings进行设置,并且分别读取被忽略的插件列表和强制启用的插件列表。
void PluginManager::setGlobalSettings(QSettings *settings)
{
d->setGlobalSettings(settings);
}
void PluginManager::setSettings(QSettings *settings)
{
d->setSettings(settings);
}
void PluginManagerPrivate::readSettings()
{
if (globalSettings) {
defaultDisabledPlugins = globalSettings->value(QLatin1String(C_IGNORED_PLUGINS)).toStringList();
defaultEnabledPlugins = globalSettings->value(QLatin1String(C_FORCEENABLED_PLUGINS)).toStringList();
}
if (settings) {
disabledPlugins = settings->value(QLatin1String(C_IGNORED_PLUGINS)).toStringList();
forceEnabledPlugins = settings->value(QLatin1String(C_FORCEENABLED_PLUGINS)).toStringList();
}
}
void PluginManagerPrivate::setPluginPaths(const QStringList &paths)
{
qCDebug(pluginLog) << "Plugin search paths:" << paths;
qCDebug(pluginLog) << "Required IID:" << pluginIID;
pluginPaths = paths;
readSettings(); //读取配置
readPluginPaths(); //读取插件目录
}
接下来就是我们的重头戏了,读取插件目录,在整个函数中,主要分为以下几步:
1、读取整个插件目录下的插件,包括子文件夹,这里用了很巧妙的设计,先是搜索插件目录下的插件,接着读取插件主目录下的插件,然后获取整个主目录下面的子目录,进行依次遍历读取所有插件,这里并没有采用递归来访问这个目录层次下的插件。
void PluginManagerPrivate::readPluginPaths()
{
qDeleteAll(pluginCategories);
qDeleteAll(pluginSpecs);
pluginSpecs.clear();
pluginCategories.clear();
QStringList pluginFiles;
QStringList searchPaths = pluginPaths;
while (!searchPaths.isEmpty()) {
const QDir dir(searchPaths.takeFirst());
const QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::NoSymLinks);
foreach (const QFileInfo &file, files) {
const QString filePath = file.absoluteFilePath();
if (QLibrary::isLibrary(filePath))
pluginFiles.append(filePath);
}
const QFileInfoList dirs = dir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot);
foreach (const QFileInfo &subdir, dirs)
searchPaths << subdir.absoluteFilePath();
}
.....
}
2、是生成所有插件的描述,读取元数据。
foreach (const QString &pluginFile, pluginFiles) {
PluginSpec *spec = new PluginSpec;
if (!spec->d->read(pluginFile)) { // not a Qt Creator plugin
delete spec;
continue;
}
...
}
在读取元数据的时候,我们可以看到之前所见到的IID,见\src\libs\extensionsystem\pluginspec.cpp
bool PluginSpecPrivate::readMetaData(const QJsonObject &metaData)
{
qCDebug(pluginLog) << "MetaData:" << QJsonDocument(metaData).toJson();
QJsonValue value;
value = metaData.value(QLatin1String("IID"));
if (!value.isString()) {
qCDebug(pluginLog) << "Not a plugin (no string IID found)";
return false;
}
//如果IID不匹配,无效
if (value.toString() != PluginManager::pluginIID()) {
qCDebug(pluginLog) << "Plugin ignored (IID does not match)";
return false;
}
....
}
//在处理完元数据之后,根据插件的类别进行分类,默认存在一个空的类别
PluginCollection *collection = 0;
// find correct plugin collection or create a new one
if (pluginCategories.contains(spec->category())) {
collection = pluginCategories.value(spec->category());
} else {
collection = new PluginCollection(spec->category());
pluginCategories.insert(spec->category(), collection);
}
...
从之前配置里面来进行插件的启用和禁用
// defaultDisabledPlugins and defaultEnabledPlugins from install settings
// is used to override the defaults read from the plugin spec
if (!spec->isDisabledByDefault() && defaultDisabledPlugins.contains(spec->name())) {
spec->setDisabledByDefault(true);
spec->setEnabled(false);
} else if (spec->isDisabledByDefault() && defaultEnabledPlugins.contains(spec->name())) {
spec->setDisabledByDefault(false);
spec->setEnabled(true);
}
if (spec->isDisabledByDefault() && forceEnabledPlugins.contains(spec->name()))
spec->setEnabled(true);
if (!spec->isDisabledByDefault() && disabledPlugins.contains(spec->name()))
spec->setEnabled(false);
将插件描述加入相应的类别之中,然后加入插件列表
collection->addPlugin(spec);
pluginSpecs.append(spec);
至此,我们已经得到了一个供我们后续加载插件所使用的插件列表。
如有错误,欢迎指正