Qt Creator插件系统分析之生成插件列表

第一次接触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);	

至此,我们已经得到了一个供我们后续加载插件所使用的插件列表。
如有错误,欢迎指正

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Qt Creator是一个跨平台的集成开发环境(IDE),用于开发基于Qt框架的应用程序。它提供了丰富的功能和工具,方便开发者进行代码编辑、调试、构建和部署等操作。Qt Creator还支持件开发,允许开发者根据自己的需求扩展和定制IDE的功能。 件开发是通过使用Qt Creator提供的API来实现的。下面是一些关键的步骤和概念: 1. 创建件项目:在Qt Creator中,可以使用"New Project"向导创建一个件项目。选择"Qt Creator Plugin"模板,并按照向导的指导完成项目的创建。 2. 件结构:件项目通常包含一个件类(继承自`ExtensionSystem::IPlugin`)和一个件描述文件(`.json`格式)。件类负责实现件的逻辑,而描述文件用于声明件的元数据和依赖关系。 3. 件接口:Qt Creator提供了一系列的接口(如`Core::IEditor`、`ProjectExplorer::IProjectManager`等),用于扩展和定制IDE的功能。件可以实现这些接口,并在适当的时机注册到Qt Creator中。 4. 件生命周期:件在Qt Creator启动时被加载,并在IDE的整个生命周期中保持活动状态。件可以通过重写件类的虚函数来响应不同的事件和操作。 5. 件通信:件之间可以通过信号和槽、接口调用等方式进行通信。Qt Creator还提供了一些全局的服务(如`Core::EditorManager`、`ProjectExplorer::SessionManager`等),件可以使用这些服务来获取和操作IDE中的资源。 如果你想深入了解Qt Creator件开发的细节,可以参考Qt Creator的官方文档和示例代码。官方文档提供了详细的API参考和开发指南,示例代码则展示了如何使用API来实现常见的件功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值