Qt Creator 源码学习 08:pluginspec.h

栏目: 编程工具 · 发布时间: 6年前

内容简介:Qt Creator 源码学习 08:pluginspec.h

前面我们已经了解到有关 Qt 中常见的 D 指针的相关内容,下面就可以开始真正的代码学习了。

首先,我们从 ExtensionSystem::PluginSpec 这个类开始。之所以选择这个类,是因为这是一个最基础的类,它代表 Qt Creator 的“一个插件”。以 Windows 平台为例,Qt Creator 的插件是以 dll 的形式存在的。我们可以打开 %QT_PATH%\Tools\QtCreator\lib\qtcreator\plugins 找到这些插件的物理文件。每一个插件都是一个动态链接库;同时,Qt Creator 可以指定这个插件是否启用,也就是是否允许在 Qt Creator 启动时加载这个插件以及更多的操作。Qt Creator 将这个动态链接库对应的物理文件抽象为 ExtensionSystem::PluginSpec 类。这个类更多的是提供给整个插件系统关于这个插件的信息,包括插件的名字、当前状态等。其中,名字这样的静态数据是嵌入到插件文件中的,作为插件的“元数据”;当前状态则是标记这个插件当前是出于加载中,还是已经加载完毕之类,是保存在内存中的动态数据。同时,如果插件出现了任务错误,其详细的错误信息都是在这个类中。因此,我们说, ExtensionSystem::PluginSpec 类表示的是与插件相关的信息,既包括静态信息,又包含动态状态。

在 Qt Creator 中,每一个插件会对应着一个 XML 文件,用于描述该插件的元信息、插件的依赖等。我们会在后面详细介绍这个 XML 文件。

ExtensionSystem::PluginSpec 相关的有三个文件:pluginspec.h、pluginspec_p.h 和 pluginspec.cpp。通过前面一章的介绍我们已经可以从文件名上面知道,这个类使用了 D 指针模式。我们从 pluginspec.h 开始读起;期间,我们将结合对应的实现文件来了解到各个函数的实现。

#pragma once
 
#include "extensionsystem_global.h"
 

我们已经见过 #pragma once 这样的写法,这里不再赘述。extensionsystem_global.h 与之前见到的 aggregation_global.h 类似,主要定义用于向外暴露的宏。然后我们注意到 pluginspec.h 类似如下的结构:

namespace ExtensionSystem {
    namespace Internal { ... } // Internal
    struct EXTENSIONSYSTEM_EXPORT PluginDependency { ... }
    struct EXTENSIONSYSTEM_EXPORT PluginArgumentDescription { ... }
    class EXTENSIONSYSTEM_EXPORT PluginSpec { ... }
} // namespace ExtensionSystem
 

虽然源文件很长,但是简单抽取出来之后我们发现,这里其实定义了两个命名空间: ExtensionSystemExtensionSystem::Internal 。顾名思义,前者是扩展系统的命名空间,而后者则是扩展系统内部实现的命名空间。所有在 ExtensionSystem::Internal 中的类,都不应该被我们自己的插件使用,因为那是内部实现类,不是暴露给外界的。Qt Creator 中很多内部使用类都是在类似 Internal 这样的命名空间中,这就是我们以后使用时需要注意的,同样也是我们的代码中可以学习的:将不希望被外界使用的类放在一个特定的命名空间中。

包括 Internal 在内的几行其实只是前置声明:

namespace Internal {
    class OptionsParser;
    class PluginSpecPrivate;
    class PluginManagerPrivate;
} // Internal
 
class IPlugin;
class PluginView;
 

接下来定义了一个可以被外部使用的类 PluginDependency

struct EXTENSIONSYSTEM_EXPORT PluginDependency
{
    enum Type {
        Required, // 必须有此依赖
        Optional, // 此依赖不是必须的
        // 在设计插件时需要注意,插件在无此依赖时应该能够正常加载
        // 比如,不能使用被依赖插件的 API 等
        Test
    };
 
    PluginDependency() : type(Required) {}
 
    QStringname; // 被依赖插件名字
    QStringversion; // 被依赖插件版本号
    Typetype; // 依赖类型
    bool operator==(const PluginDependency &other) const;
        QStringtoString() const;
    };
 
    uintqHash(const ExtensionSystem::PluginDependency &value);
 

一个插件可能建立在其它插件的基础之上。如果插件 A 必须在插件 B 加载成功之后才能够加载,那么我们就说,插件 A 依赖于插件 B,插件 B 是插件 A 的被依赖插件。按照这一的加载模式,最终 Qt Creator 会得到一棵插件树。 PluginDependency 定义了有关被依赖插件的信息,包括被依赖插件的名字以及版本号等。我们使用 PluginDependency 定义所需要的依赖,Qt Creator 则根据我们的定义,利用 Qt 的反射机制,通过名字和版本号获取到插件对应的状态,从而获知被依赖插件是否加载之类的信息。值得注意的是,Qt Creator 在匹配版本号时,并不会直接按照这里给出的 version 值完全匹配,而是按照一定的算法,选择一段区间内兼容的版本。这样做的目的是,有些插件升级了版本号之后,另外的插件可以按照版本号兼容,不需要一同升级。

qHash() 函数是一个全局函数,用于计算 PluginDependency 类的散列值。该函数的作用是允许 PluginDependency 类作为 QHash 这样的集合类的键。按照 QHash 文档要求, QHash 的键必须在其类型所在命名空间中同时提供 operator==() 以及 qHash() 函数。有关具体细节,可以参考 QHash 的相关文档。在这里,这个函数的实现相当简单:只是计算了 name 属性的散列值:

uintExtensionSystem::qHash(const PluginDependency &value)
{
    return qHash(value.name);
}
 

下面的 PluginArgumentDescription 是一个简单的数据类,用于描述插件参数。Qt Creator 的插件可以在启动时提供额外的参数,类似 main() 函数参数的作用。

struct EXTENSIONSYSTEM_EXPORT PluginArgumentDescription
{
    QStringname;
    QStringparameter;
    QStringdescription;
};
 

然后,我们可以看到最主要的 PluginSpec 类的定义:

class EXTENSIONSYSTEM_EXPORT PluginSpec
{
public:
    enum State { Invalid, Read, Resolved, Loaded, Initialized, Running, Stopped, Deleted};
    ~PluginSpec();
 

首先,是一个 State 枚举,用于指示插件加载时的状态。当插件加载失败时,我们可以根据插件的状态来判断是哪个环节出了问题。这些状态的含义如下:

状态 含义
Invalid 起始点:任何信息都没有读取,甚至连插件元数据都没有读到
Read 成功读取插件元数据,并且该元数据是合法的;此时,插件的相关信息已经可用
Resolved 插件描述文件中给出的各个依赖已经被成功找到,这些依赖可以通过 dependencySpecs() 函数获取
Loaded 插件的库已经加载,插件实例成功创建;此时插件实例可以通过 plugin() 函数获取
Initialized 调用插件实例的 IPlugin::initialize() 函数,并且该函数返回成功
Running 插件的依赖成功初始化,并且调用了 extensionsInitialized() 函数;此时,加载过程完毕
Stopped 插件已经停止,插件的 IPlugin::aboutToShutdown() 函数被调用
Deleted 插件实例被删除销毁

头文件提供了很多函数。这些函数有的是访问函数,很多数据是从插件对应的 XML 文件中读取的。

// 插件名字。当状态达到 PluginSpec::Read 时才可用。
QStringname() const;
// 插件版本。当状态达到 PluginSpec::Read 时才可用。
QStringversion() const;
// 插件兼容版本。当状态达到 PluginSpec::Read 时才可用。
QStringcompatVersion() const;
// 插件提供者。当状态达到 PluginSpec::Read 时才可用。
QStringvendor() const;
// 插件版权。当状态达到 PluginSpec::Read 时才可用。
QStringcopyright() const;
// 插件协议。当状态达到 PluginSpec::Read 时才可用。
QStringlicense() const;
// 插件描述。当状态达到 PluginSpec::Read 时才可用。
QStringdescription() const;
// 插件主页 URL。当状态达到 PluginSpec::Read 时才可用。
QStringurl() const;
// 插件类别,用于在界面分组显示插件信息。如果插件不属于任何类别,直接返回空字符串。
QStringcategory() const;
// 插件兼容的平台版本的正则表达式。如果兼容所有平台,则返回空。
QRegExpplatformSpecification() const;
// 对于宿主平台是否可用。该函数用使用 platformSpecification() 的返回值对平台名字进行匹配。
bool isAvailableForHostPlatform() const;
// 是否必须。
bool isRequired() const;
// 是否实验性质的插件。
bool isExperimental() const;
// 默认启用。实验性质的插件可能会被禁用。
bool isEnabledByDefault() const;
// 因配置信息启动。
bool isEnabledBySettings() const;
// 是否在启动时已经加载。
bool isEffectivelyEnabled() const;
// 因为用户取消或者因其依赖项被取消而导致该插件无法加载时,返回 true。
bool isEnabledIndirectly() const;
// 是否通过命令行参数 -load 加载。
bool isForceEnabled() const;
// 是否通过命令行参数 -noload 禁用。
bool isForceDisabled() const;
// 插件依赖列表。当状态达到 PluginSpec::Read 时才可用。
QVectordependencies() const;
 
typedef QVectorPluginArgumentDescriptions;
// 插件处理的命令行参数描述符列表。
PluginArgumentDescriptionsargumentDescriptions() const;
 
// 其它信息,当状态达到 PluginSpec::Read 时才可用。
// 该 PluginSpec 实例对应的插件 XML 描述文件所在目录的绝对位置。
QStringlocation() const;
// 该 PluginSpec 实例对应的插件 XML 描述文件的绝对位置(包含文件名)。
QStringfilePath() const;
 
// 插件命令行参数。启动时设置。
QStringListarguments() const;
// 设置插件命令行参数为 arguments。
void setArguments(const QStringList &arguments);
// 将 argument 添加到插件的命令行参数。
void addArgument(const QString &argument);
 
// 当一个依赖需要插件名为 pluginName、版本为 version 时,返回该插件是否满足。
bool provides(const QString &pluginName, const QString &version) const;
 
// 插件的依赖。当状态达到 PluginSpec::Resolved 时才可用。
QHash<PluginDependency, PluginSpec *> dependencySpecs() const;
// 否则依赖 plugins 集合中的任一插件。
bool requiresAny(const QSet &plugins) const;
 
// PluginSpec 实例对应的 IPlugin 实例。当状态达到 PluginSpec::Loaded 时才可用。
IPlugin *plugin() const;
 
// 当前状态。
Statestate() const;
// 是否发生错误。
bool hasError() const;
// 错误信息。
QStringerrorString() const;
 

现在,我们用了很长的篇幅,阐明了 pluginspec.h 中所有公共函数的主要作用。很多函数只需要看名字就能明白其作用,不过我们还是不厌其烦地写明。这是因为 PluginSpec 实在是一个非常重要的基础类。插件的主要信息都是通过这个类获取的,所以,我们有必要详细介绍这个类的对外接口。从静态意义上说, PluginSpec 主要保存了插件的元数据,比如版本号、协议、类别等;从动态意义上说, PluginSpec 记录了插件的加载状态。

函数列表的最后, hasError()errorString() 是 Qt 中常见的函数。前者描述是否有错误发生;后者描述如果有错误,则具体的错误信息是什么。如果熟悉 Qt SQL 编程,SQL 模块很多错误都是用这种方法通知的。这样的错误处理避免了 C 风格的用 int 作为返回值,同时使用 char ** 接收错误信息的机制。Qt 的处理模式显然更为简洁。

最后,私有变量部分:

private:
    PluginSpec();
 
    Internal::PluginSpecPrivate *d;
    friendclass PluginView;
    friendclass Internal::OptionsParser;
    friendclass Internal::PluginManagerPrivate;
    friendclass Internal::PluginSpecPrivate;
};
 

注意, PluginSpec 的构造函数是私有的。这意味着我们不能创建其实例。这个类显然不是单例,并且明显没有提供 static 的工厂函数,那么,我们如何创建其实例呢?答案就是,我们不能: PluginSpec 的实例只能通过 Qt Creator 自身创建,而能够创建的类,就是这里定义的友元类。这里其实使用了 C++ 语言特性,即友元类可以访问到私有函数。我们将 Internal::PluginManagerPrivate 设置为 PluginSpec 的友元,就可以通过这个类调用 PluginSpec 私有的构造函数,从而创建其实例。这一技巧依赖于 C++ 语言特性,不能推广到其它语言,不过如果你使用的正是 C++,那么不妨尝试使用这一技巧,实现一种只能通过系统本身才能实例化的类。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

代码整洁之道

代码整洁之道

[美]Robert C. Martin / 韩磊 / 人民邮电出版社 / 2010-1-1 / 59.00元

软件质量,不但依赖于架构及项目管理,而且与代码质量紧密相关。这一点,无论是敏捷开发流派还是传统开发流派,都不得不承认。 本书提出一种观念:代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。作为编程领域的佼佼者,本书作者给出了一系列行之有效的整洁代码操作实践。这些实践在本书中体现为一条条规则(或称“启示”),并辅以来自现实项目的正、反两面的范例。只要遵......一起来看看 《代码整洁之道》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具