内容简介: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++,那么不妨尝试使用这一技巧,实现一种只能通过系统本身才能实例化的类。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Programming in Haskell
Graham Hutton / Cambridge University Press / 2007-1-18 / GBP 34.99
Haskell is one of the leading languages for teaching functional programming, enabling students to write simpler and cleaner code, and to learn how to structure and reason about programs. This introduc......一起来看看 《Programming in Haskell》 这本书的介绍吧!