内容简介: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
虽然源文件很长,但是简单抽取出来之后我们发现,这里其实定义了两个命名空间: ExtensionSystem
和 ExtensionSystem::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++,那么不妨尝试使用这一技巧,实现一种只能通过系统本身才能实例化的类。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。