首页 > 代码库 > Qt5的插件机制(1)--Qt 框架中的插件加载机制概述
Qt5的插件机制(1)--Qt 框架中的插件加载机制概述
概述
Qt的源码中通过 Q<pluginType>Factory、Q<pluginType>Plugin 和 Q<pluginType> 这三个类实现了Qt的插件加载机制,
这个机制可用于加载特定种类的插件。比如通过 QPlatformIntegrationFactory\QPlatformIntegrationPlugin\QPlatformIntegration三个类可以实现平台类QPA插件(PlatformIntegration)的加载,通过QPlatformInputContextFactory\QPlatformInputContextPlugin\
QPlatformInputContext三个类可以实现输入法类插件(InputContext)的加载。
下面自底向上介绍Qt的插件加载机制
实现插件:Q<pluginType> 类和Q<pluginType>Plugin 类
首先,Q<pluginType> 类(或其子类)实现具体的功能,他是插件的主体,不同类别的插件要实现不同的功能;Q<pluginType>Plugin 类(或其子类)是该插件的接口类, 一般只需要实现一个方法,creat,
class Q<pluginType>Plugin { ... Q<pluginType> * creat(...) ; // 返回一个Q<pluginType>类型的指针,这个函数的功能一般都非常简单, // 其内部只需要 new 一个 Q<pluginName> 类的对象,并返回其指针 }
Q<pluginType>Plugin 类主要被 Qt 框架自身用来加载插件
加载插件:QFactoryLoader 类
此外还有一个类,与插件的加载息息相关,这个类是 QFactoryLoader, 我们现在只需要关心这个类的 instance() 方法:QFactoryLoader::QObject *instance(int index)
QFactoryLoader 类会维护一个 库列表, index 就是要加载的插件所属的库在库列表中的索引。instance 返回一个
QOjbect类或其子类的指针,而这个指针一般会被映射成 Q<pluginType>Plugin 类型。
这样一来,Qt 框架要加载某个插件时,只需要先调用 QFactoryLoader::instance(index) 返回一个该插件对应的
Q<pluginType>Plugin指针,再通过这个指针调用 Q<pluginType>Plugin::creat(...) 方法,就可以获得插件主体
类Q<pluginName>的一个实例。
加载插件的快捷函数
接着再来看下 qLoadPlugin 和 qLoadPlugin1 这两个模板函数的实现,二者功能上的主要区别是后者可以设置加载插件时的参数。使用这两个模板函数可以快捷的加载插件并获取其实例。
在使用这两个模板时,模板类型参数 PluginInterface 应被填充为 Q<pluginType>(插件主体类),类型参数FactoryInterface
应被填充为 Q<pluginType>Plugin类。
template <class PluginInterface, class FactoryInterface> PluginInterface *qLoadPlugin(const QFactoryLoader *loader, const QString &key) { const int index = loader->indexOf(key); // 根据插件的关键字查找该插件所属的库在库列表中的索引 if (index != -1) { QObject *factoryObject = loader->instance(index); // 加载插件所属的库 if (FactoryInterface *factory = qobject_cast<FactoryInterface *>(factoryObject)) if (PluginInterface *result = factory->create(key)) return result; // 返回插件的实体类 } return 0; } template <class PluginInterface, class FactoryInterface, class Parameter1> PluginInterface *qLoadPlugin1(const QFactoryLoader *loader, const QString &key, const Parameter1 ¶meter1) { const int index = loader->indexOf(key); if (index != -1) { QObject *factoryObject = loader->instance(index); if (FactoryInterface *factory = qobject_cast<FactoryInterface *>(factoryObject)) if (PluginInterface *result = factory->create(key, parameter1)) return result; } return 0; }
插件生产者:Q<pluginType>Factory 类
最后来看Q<pluginType>Factory 类,它是Qt的插件加载机制中位于最上层的类,这个类一般主要实现两个静态的方法(我们更关心 creat),而且都是静态的。其定义大致如下:
class Q<pluginType>Factory { public: static QStringList keys(...) ; // 获得与 Q<pluginType> 类型的插件相关的关键字列表, // 关键字一般用于描述插件的名称等属性,这个关键字列表中的每个 // 元素都对应一个实际的插件。如 QPlatformInputContextFactory::keys(...) // 返回的就是输入法插件的名称列表。 static Q<pluginType> * creat(...) ; // 返回一个Q<pluginType>类型的指针(应用程序所需的插件) }
Q<pluginType>Factory::creat(...) 函数中,会通过检测环境变量等手段来获取到与相应的插件相关的关键字,然后再用该关键字
去调用 qLoadPlugin/qLoadPlugin1 或相似的方法来加载插件,最后返回一个插件实体类。
一个Q<pluginType>Factory 类往往对应于某一类别、或某种特定功能的插件。比如QPlatformIntegrationFactory用于“生产”平台类插件,
QPlatformInputContextFactory用于“生产”输入法类插件。一般情况下,Q<pluginType>Factory 类并没有实际的成员变量,而只有几个
静态的方法,因此一个Qt应用程序中不需要将这个类实例化,而是直接使用这个类的静态方法。另外,Qt还会为每个Q<pluginType>Factory 类
绑定一个 QFactoryLoader 对象,这个对象专门负责加载这一类别的插件。
Qt 框架的顶层要加载某一类插件时,只需与对应的Q<pluginType>Factory 类打交道即可。比如,在Qt应用程序初始化过程中,它发现自己
需要一个输入法插件了,就会直接调用 QPlatformInputContextFactory::creat(...) 来生产一个输入法类插件,而不用再管这个插件加载过程中的细节。
返回的这个输入法类插件到底是什么,是ibus? 还是fcitx? 这些完全由用户通过环境变量QT_IM_MODULE指定,
QPlatformInputContextFactory::create()方法中会去检测这个环境变量。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
附 1 : Qt应用程序的平台类插件QPlatformIntegration的加载过程
现在暂时先把焦点转移到QGuiApplicationPrivate::platformIntegration()上,这个方法的定义为:
static QPlatformIntegration *platform_integration; static QPlatformIntegration *platformIntegration() { return platform_integration; }
可见 platform_integration 指向了当前的运行平台,那这个代表平台的成员在何处被初始化呢?
在QGuiApplication类(每个GUI应用程序都有一个它的实例)的构造函数中,会调用QGuiApplicationPrivate类的init()方法,
而 QGuiApplicationPrivate::init 又会调用 QGuiApplicationPrivate::createPlatformIntegration(), 后者会读取
QT_QPA_PLATFORM_PLUGIN_PATH \ QT_QPA_PLATFORM \ QT_QPA_PLATFORMTHEME 等环境变量,接着将他们传递给
init_platform(...)函数 ,这个函数定义于 qtbase/src/gui/kernel/qguiapplication.cpp 文件中,它内部有一句:
GuiApplicationPrivate::platform_integration = QPlatformIntegrationFactory::create(name, arguments, argc, argv, platformPluginPath); ....
QPlatformIntegrationFactory::create 里又通过下面这句加载平台插件
QPlatformIntegration *ret = loadIntegration(directLoader(), platform, paramList, argc, argv))
loadIntegration函数定义在 qtbase/src/gui/kernel/QPlatformIntegrationFactory.cpp中, 这个函数的作用和实现方法都与模板函数 qLoadPlugin 的实现类似。
static inline QPlatformIntegration *loadIntegration(QFactoryLoader *loader, const QString &key, const QStringList ¶meters, int &argc, char ** argv) { const int index = loader->indexOf(key); if (index != -1) { // factory 指向对应的平台插件类的实例,如QLinuxFbIntegrationPlugin类; // 接着调用其creat方法生成并返回一个 QPlatformIntegration 类的实例的指针, // 这个指针将最终赋值给 QGuiApplicationPrivate::platform_integration, // 应用程序就得到了自己的运行平台. // 同时由此可知,如果想自己写一个QPA平台插件,只需派生一个QPlatformIntegrationPlugin类和一个QPlatformIntegration类即可。 if (QPlatformIntegrationPlugin *factory = qobject_cast<QPlatformIntegrationPlugin *>(loader->instance(index))) if (QPlatformIntegration *result = factory->create(key, parameters, argc, argv)) return result; } return 0; }
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
附 2 : Qt应用程序的输入法类插件QPlatformInputContext的加载过程
Linux xcb平台或linuxfb平台都是在平台插件 (QXcbIntegration或QLinuxFbIntegration类 ) 的initialize() 函数中 通过
调用 QPlatformInputContextFactory::create() 来初始化 平台的输入法插件(QPlatformInputContext类)的。
QPlatformInputContextFactory::create() 的实现如下:
QPlatformInputContext *QPlatformInputContextFactory::create() { QPlatformInputContext *ic = 0; QString icString = QString::fromLatin1(qgetenv("QT_IM_MODULE")); // 检测环境变量QT_IM_MODULE,根据它选择要加载的输入法插件 if (icString == QLatin1String("none")) return 0; ic = create(icString); // 调用另一个create函数加载输入法插件 if (ic && ic->isValid()) return ic; // 下面的代码暂不理会 delete ic; ic = 0; QStringList k = keys(); for (int i = 0; i < k.size(); ++i) { if (k.at(i) == icString) continue; ic = create(k.at(i)); if (ic && ic->isValid()) return ic; delete ic; ic = 0; } return 0; } QPlatformInputContext *QPlatformInputContextFactory::create(const QString& key) { QStringList paramList = key.split(QLatin1Char(':')); const QString platform = paramList.takeFirst().toLower(); #if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) if (QPlatformInputContext *ret = qLoadPlugin1<QPlatformInputContext, QPlatformInputContextPlugin>(loader(), platform, paramList)) // 根据key(插件名称)来加载对应的库并实例化(产生一个QPlatformInputContext类型的指针)。 return ret; #endif return 0; }
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
附 3 : 几点提示
<1>
Qt中能加载库或插件的几个类:
QLibrary ,
QPluginLoader ,
QFactoryLoader ,
QStaticPlugin (暂时不研究这个)
QLibrary 和 QPluginLoader 依赖的‘私有数据类‘都是 QLibraryPrivate, 一个QLibrary或QPluginLoader的对象都有一个QLibraryPrivate对象,对应一个库或插件;
QFactoryLoader 依赖的‘私有数据类‘是 QFactoryLoaderPrivate , 但 QFactoryLoaderPrivate 类中又包含了一个QLibraryPrivate列表,这个列表中有多个
QLibraryPrivate类型的元素,对应一系列的库或插件;
所以可见,QLibraryPrivate是Qt中与库或插件相关的核心数据类,每个库都对应一个QLibraryPrivate对象。
<2>
1. Qt Assistant 中搜索 How to Create Qt Plugins ,这一段详细说明了创建插件的方法。
主要有高级和低级两种API可以用来写插件。
高级API的使用方法可以在Qt源码中看到很多实例。低级API的使用示例在本系列文章的最后给出。
2. 如果不会写插件的 .pro 文件,可以在Qt Assistant 中搜索 qmake Manual , 这一页里有很多链接是与编写工程文件相关的,如
qmake Language 链接讲 .pro 文件的语法,Variables 链接讲.pro 文件中的变量(如QT、CONFIG、TEMPLATE等变量),
Replace Functions 和 Replace Functions 链接讲一些内建的函数(可以在.pro文件中使用)
Qt5的插件机制(1)--Qt 框架中的插件加载机制概述