Qt5的插件机制(2)--QxxxFactory类与QFactoryLoader类

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

QxxxFactory类:插件生产者


在Qt的插件加载机制的概述中,我已经提到过,一个Q<pluginType>Factory 类往往对应于某一类别、或某种特定功能的插件。
在Qt中,为了区分不同类别、不同功能的插件,应该为每一类插件设置一个独特的 IID 值,这个IID值通常
是一个长字符串。属于同一类的插件应该具有相同的IDD值。比如,所有平台类QPA插件,包括LinuxFB插件(QLinuxFbIntegration)、
XCB插件(QXcbIntegration)等,他们的IDD都应是 org.qt-project.Qt.QPA.QPlatformIntegrationFactoryInterface.5.2 ,
而所有的输入法类插件,如Qt的ibus插件、fcitx插件等,他们的IDD都应该是 org.qt-project.Qt.QPlatformInputContextFactoryInterface。

另外我提到过,Qt还会为每个Q<pluginType>Factory 类,即每一个类别的插件,绑定一个 QFactoryLoader 对象,
这个QFactoryLoader 对象中也记录了这一类插件的IID的值,专门负责加载这一类别的插件。

接下来,就分别研究一下这两个类。先看 QxxxFactory。这里为了具体一点,我们拿QPlatformInputContextFactory类来讲,其他的QxxxFactory
类也都是类似的。


[cpp] view plain
  1. class Q_GUI_EXPORT QPlatformInputContextFactory  
  2. {  
  3. public:  
  4.     static QStringList keys();        // 只有几个静态的成员函数  
  5.     static QPlatformInputContext *create(const QString &key);  
  6.     static QPlatformInputContext *create();  
  7. };  



其中,keys() 方法用于获得所有同类插件的关键字。对于QPlatformInputContextFactory类,它的keys()方法自然就是获得所有输入法类插件,比如ibus\fcitx等插件的关键字。
[cpp] view plain
  1. QStringList QPlatformInputContextFactory::keys()  
  2. {  
  3. #if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)  
  4.     return loader()->keyMap().values();  
  5. #else  
  6.     return QStringList();  
  7. #endif  
  8. }  

获取关键字的这段代码很短,但我们马上就有个疑问,代码中出现的 loader() 是哪儿来的?它的返回值是什么?
我最初读到这段代码时也一致纠结于此,因为哪里都找不到这个 loader() 的定义,后来才注意到,在这个源文件的
开头,有一句
[cpp] view plain
  1. #if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)  
  2. Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader,   // <--- loader就在这里定义  
  3.     (QPlatformInputContextFactoryInterface_iid, QLatin1String("/platforminputcontexts"), Qt::CaseInsensitive))  
  4. #endif  



这才有点眉目,而且在Qt Assistanct中也找到了 Q_GLOBAL_STATIC_WITH_ARGS 这个宏的说明,这个宏专门用于定义全局的静态变量,
Q_GLOBAL_STATIC_WITH_ARGS( Type, VariableName, Arguments)
其中Type是要定义的静态全局变量的类型,VariableName是变量的名字,Arguments是构造参数。使用这个宏,可以定义一个变量为
VariableName、类型为 QGlobalStatic 的对象。而 QGlobalStatic 类重载了括号运算符 '()',通过VariableName()可以得到一个
Type类型的指针,他指向的就是我们所需要的那个静态全局变量。Arguments就是在构造这个Type类型的静态全局对象时传给构造函数
的参数。
因此上面那句代码,就是定义了一个 QFactoryLoader 类的静态全局对象,这样在之后的代码中,就可以通过 loader() 访问这个对象了。
loader()->keyMap().values() 这一行,就把这个 QFactoryLoader 对象对应的所有插件的关键字获取到组成一个列表,本文后面会介绍QFactoryLoader类。

QPlatformInputContextFactory 有两个 create 方法,其中一个是指定了关键字,另一个则自己搜索关键字。

[cpp] view plain
  1. QPlatformInputContext *QPlatformInputContextFactory::create(const QString& key)  
  2. {  
  3.     // 指定了关键字 key   
  4.     QStringList paramList = key.split(QLatin1Char(':'));    // 将关键字按分隔符分离成一个或多个参数,分隔符是冒号  
  5.     const QString platform = paramList.takeFirst().toLower();    // 获取key中分离的第一个参数,作为加载插件时用的关键字  
  6.   
  7. #if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)  
  8.     if (QPlatformInputContext *ret = qLoadPlugin1<QPlatformInputContext, QPlatformInputContextPlugin>(loader(), platform, paramList))  
  9.         return ret;  
  10. #endif  
  11.     return 0;  
  12. }  
  13.   
  14.   
  15.   
  16. QPlatformInputContext *QPlatformInputContextFactory::create()  
  17. {  
  18.     // 未指定关键字,则自己搜索关键字。  
  19.     QPlatformInputContext *ic = 0;  
  20.   
  21.     // 先从环境变量中搜索关键字  
  22.     QString icString = QString::fromLatin1(qgetenv("QT_IM_MODULE"));  
  23.   
  24.     if (icString == QLatin1String("none"))  
  25.         return 0;  
  26.   
  27.     ic = create(icString);  
  28.     if (ic && ic->isValid())  
  29.         return ic;  
  30.   
  31.     delete ic;  
  32.     ic = 0;  
  33.   
  34.     // 如果环境变量中找不到合适的关键字,则从 keys() 返回的关键字列表中一个一个试  
  35.     QStringList k = keys();  
  36.     for (int i = 0; i < k.size(); ++i) {  
  37.         if (k.at(i) == icString)  
  38.             continue;  
  39.         ic = create(k.at(i));  
  40.         if (ic && ic->isValid())  
  41.             return ic;  
  42.         delete ic;  
  43.         ic = 0;  
  44.     }  
  45.   
  46.     return 0;  
  47. }  


<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

QFactoryLoader 类:插件加载者


如前所说,一个 QFactoryLoader 对象用于加载某一类别的插件。QFactoryLoader 类中维护了一个
插件/库列表(列表中的元素都是QLibraryPrivate类型),这个列表中的插件/库都是属于同一类别的插件,
他们的 IID 都是一样的(一个插件/库的IID值存储在其QLibraryPrivate对象的元信息metaData中)。
除了这个插件/库列表,QFactoryLoader 类中还维护了一个从关键字到插件/库的映射表,通过这个映射表
可以快速的通过关键字来找到对应的库。

这里我们先来看下QFactoryLoaderPrivate的定义:

[cpp] view plain
  1. class QFactoryLoaderPrivate : public QObjectPrivate  
  2. {  
  3.     Q_DECLARE_PUBLIC(QFactoryLoader)  
  4. public:  
  5.     QFactoryLoaderPrivate(){}    // 构造函数什么也不做  
  6.     ~QFactoryLoaderPrivate();    // 析构函数中卸载并释放插件/库列表中所有的库  
  7.                 /*    QFactoryLoaderPrivate::~QFactoryLoaderPrivate() 
  8.                     { 
  9.                         for (int i = 0; i < libraryList.count(); ++i) { 
  10.                         QLibraryPrivate *library = libraryList.at(i); 
  11.                         library->unload(); 
  12.                         library->release(); 
  13.                         } 
  14.                     }    */  
  15.   
  16.     mutable QMutex mutex;  
  17.     QByteArray iid;        // 当前对象对应的插件集的 IID  
  18.     QList<QLibraryPrivate*> libraryList;    // 插件/库 列表  
  19.     QMap<QString,QLibraryPrivate*> keyMap;    // 插件/库 到 关键字的映射表  
  20.     QString suffix;                // 当前对象对应的插件集中所有插件/库对应的库文件(.so文件)路径的后缀(最低一级目录的名字)  
  21.     Qt::CaseSensitivity cs;        // 匹配关键字的策略,是精确匹配还是粗略匹配,一般都选粗略匹配  
  22.     QStringList loadedPaths;    // 所有已经加载过的库路径  
  23.   
  24.     void unloadPath(const QString &path);  
  25. };  

QFactoryLoaderPrivate类的iid、suffix和cs成员,实在QFactoryLoader类的构造函数中初始化的,下面马上会看到。


QFactoryLoader类的定义如下。

[cpp] view plain
  1. class Q_CORE_EXPORT QFactoryLoader : public QObject  
  2. {  
  3.     Q_OBJECT  
  4.     Q_DECLARE_PRIVATE(QFactoryLoader)  
  5.   
  6. public:  
  7.     explicit QFactoryLoader(const char *iid,  
  8.                    const QString &suffix = QString(),  
  9.                    Qt::CaseSensitivity = Qt::CaseSensitive);      
  10.             // 构造函数,先设置插件集的 IID、插件路径后缀和关键字匹配策略,  
  11.             // 再调用update()生成插件列表和插件到关键字的映射表,并将当前的  
  12.             // QFactoryLoader对象添加到全局的loader列表qt_factory_loaders中 。  
  13.   
  14.     ~QFactoryLoader();        // 析构,将当前的QFactoryLoader对象从全局的loader列表qt_factory_loaders中移除  
  15.   
  16.     QList<QJsonObject> metaData() const;    // 返回d->libraryList(库列表)中每个库的元信息组成的列表(列表的还加上了静态库的元信息)  
  17.     QObject *instance(int index) const;        // 返回d->libraryList(库列表)第index个插件的实例  
  18.   
  19. #if defined(Q_OS_UNIX) && !defined (Q_OS_MAC)  
  20.     QLibraryPrivate *library(const QString &key) const;    // 返回映射表中关键字key对应的库  
  21. #endif  
  22.   
  23.     QMultiMap<int, QString> keyMap() const;    // 返回d->libraryList(库列表)中所有库的关键字映射表:每个库有  
  24.                         // 一个int型索引(就是该库在库列表中的索引)和若干个QString  
  25.                         // 类型的关键字,这个函数的返回值是QMultiMap类型的一个“一对多”的映射表,一个  
  26.                         // 库(索引)对应若干个关键字。  
  27.   
  28.     int indexOf(const QString &needle) const;    // 返回关键字needle对应的库在库列表中的索引  
  29.   
  30.     void update();    // 主要功能就是更新 库列表(d->libraryList) 和 关键字到库的映射表(d->keyMap), 一般在应用程序的库加载路径发生变化时才需调用  
  31.   
  32.     static void refreshAll();    // 这是一个静态函数,用于将全局loader列表(qt_factory_loaders)中的所有QFactoryLoader对象都update() 一下  
  33. };  


通过上面的注释我们已经知道,库列表(d->libraryList) 和 关键字到库的映射表(d->keyMap)都是在构造函数中通过调用update()生成的,
这两个表几乎就是这个类的核心,下面我们就看看这两个表是如何生成的。

[cpp] view plain
  1. void QFactoryLoader::update()  
  2. {  
  3. #ifdef QT_SHARED  
  4.     Q_D(QFactoryLoader);  
  5.     QStringList paths = QCoreApplication::libraryPaths();    // 获取应用程序的库加载路径  
  6.   
  7.     // 第一层循环, 开始遍历所有的库路径  
  8.     for (int i = 0; i < paths.count(); ++i) {  
  9.   
  10.         const QString &pluginDir = paths.at(i);        // 获取第 i 个库路径  
  11.   
  12.         // Already loaded, skip it... 如果当前库路径已经记录在了 d->loadedPaths 中,则跳过这个库路径。  
  13.         if (d->loadedPaths.contains(pluginDir))  
  14.             continue;  
  15.         d->loadedPaths << pluginDir;    // 将当前库路径添加到 d->loadedPaths 中。这样可以防止重复加载一个库路径  
  16.   
  17.         QString path = pluginDir + d->suffix;    // 这个库路径加上当前QFactoryLoader对象对应的插件集的路径后缀,得到插件集的路径path(如果存在)  
  18.   
  19.         if (qt_debug_component())  
  20.             qDebug() << "QFactoryLoader::QFactoryLoader() checking directory path" << path << "...";  
  21.   
  22.         if (!QDir(path).exists(QLatin1String(".")))    // 检测插件集路径path是否存在,不存在则跳过当前的库路径  
  23.             continue;  
  24.   
  25.   
  26.         // 如果插件集路径path存在,接着往下运行  
  27.   
  28.         QStringList plugins = QDir(path).entryList(QDir::Files);    // 列出当前插件集路径path中的所有文件名,存入plugins列表中  
  29.         QLibraryPrivate *library = 0;  
  30.   
  31. #ifdef Q_OS_MAC  
  32.         // Loading both the debug and release version of the cocoa plugins causes the objective-c runtime  
  33.         // to print "duplicate class definitions" warnings. Detect if QFactoryLoader is about to load both,  
  34.         // skip one of them (below).  
  35.         //  
  36.         // ### FIXME find a proper solution  
  37.         //  
  38.         const bool isLoadingDebugAndReleaseCocoa = plugins.contains(QStringLiteral("libqcocoa_debug.dylib"))  
  39.                 && plugins.contains(QStringLiteral("libqcocoa.dylib"));  
  40. #endif  
  41.   
  42.         // 第二层循环,开始遍历插件集路径path中的所有文件  
  43.         for (int j = 0; j < plugins.count(); ++j) {  
  44.   
  45.             QString fileName = QDir::cleanPath(path + QLatin1Char('/') + plugins.at(j)); // 获取第 j 个文件的文件名  
  46.   
  47. #ifdef Q_OS_MAC  
  48.             if (isLoadingDebugAndReleaseCocoa) {  
  49. #ifdef QT_DEBUG  
  50.                if (fileName.contains(QStringLiteral("libqcocoa.dylib")))  
  51.                    continue;    // Skip release plugin in debug mode  
  52. #else  
  53.                if (fileName.contains(QStringLiteral("libqcocoa_debug.dylib")))  
  54.                    continue;    // Skip debug plugin in release mode  
  55. #endif  
  56.             }  
  57. #endif  
  58.             if (qt_debug_component()) {  
  59.                 qDebug() << "QFactoryLoader::QFactoryLoader() looking at" << fileName;  
  60.             }  
  61.   
  62.         // 尝试根据文件名fileName创建库library (库用QLibraryPrivate类的对象表示)   
  63.         // 疑问: 如果插件集路径中包含有非库的普通文件(比如文本文件、图片),也要为他们生成一个 library ? 所以不应该在插件集路径中放非库的文件?  
  64.             library = QLibraryPrivate::findOrCreate(QFileInfo(fileName).canonicalFilePath());      
  65.   
  66.             if (!library->isPlugin()) {          
  67.         // 判断该库(library)是不是插件,如果不是插件,则跳过当前的库  
  68.                 if (qt_debug_component()) {  
  69.                     qDebug() << library->errorString;  
  70.                     qDebug() << "         not a plugin";  
  71.                 }  
  72.                 library->release();// 释放该库  
  73.                 continue;  
  74.             }  
  75.   
  76.         // 如果该库(library)是插件,则继续往下运行  
  77.             QStringList keys;  
  78.             bool metaDataOk = false;  
  79.   
  80.             QString iid = library->metaData.value(QLatin1String("IID")).toString();  
  81.   
  82.             if (iid == QLatin1String(d->iid.constData(), d->iid.size())) {  
  83.   
  84.         // 如果插件library的IID与当前QFactoryLoader对象对应的插件集的IID相同,则设置metaDataOk标志,并  
  85.         // 读取该插件library的元信息中的 MetaData:Keys 字段,这个字段是个JSON数组类型,  
  86.         // 存储了若干字符串类型的关键字,将这些关键字天价到字符串列表 keys 中。  
  87.   
  88.                 QJsonObject object = library->metaData.value(QLatin1String("MetaData")).toObject();  
  89.                 metaDataOk = true;    // 设置metaDataOk标志  
  90.                 QJsonArray k = object.value(QLatin1String("Keys")).toArray();  
  91.                 for (int i = 0; i < k.size(); ++i)  
  92.                     keys += d->cs ? k.at(i).toString() : k.at(i).toString().toLower();  
  93.             }  
  94.             if (qt_debug_component())  
  95.                 qDebug() << "Got keys from plugin meta data" << keys;  
  96.   
  97.   
  98.             if (!metaDataOk) {    // 如果metaDataOk标志未被设置,说明IID不匹配,跳过library这个插件  
  99.                 library->release();  
  100.                 continue;  
  101.             }  
  102.   
  103.             int keyUsageCount = 0;    // 映射计数,对映射到 library 上的关键字计数  
  104.   
  105.         // 第三层小循环,遍历library元信息中Keys字段存储的所有关键字  
  106.         // 这个循环建立了 库到关键字的映射表(的一部分)  
  107.             for (int k = 0; k < keys.count(); ++k) {  
  108.                 // first come first serve, unless the first  
  109.                 // library was built with a future Qt version,  
  110.                 // whereas the new one has a Qt version that fits  
  111.                 // better  
  112.                 const QString &key = keys.at(k);    // 获取第k个关键字key  
  113.   
  114.             // 如果映射表已经映射过 key 了,则获取其之前映射的那个库previous及其所用的Qt版本号prev_qt_version,  
  115.             // 跟当前库library的Qt版本号qt_version进行比较,如果prev_qt_version高于当前的QT版本号并且qt_version  
  116.             // 不大于当前的QT版本号,则将关键字 key 重新映射到 library 。   
  117.             // 如果映射表中还未映射过关键字key,则直接将其映射到 library   
  118.                 QLibraryPrivate *previous = d->keyMap.value(key);      
  119.                 int prev_qt_version = 0;  
  120.                 if (previous) {  
  121.                     prev_qt_version = (int)previous->metaData.value(QLatin1String("version")).toDouble();  
  122.                 }  
  123.                 int qt_version = (int)library->metaData.value(QLatin1String("version")).toDouble();  
  124.                 if (!previous || (prev_qt_version > QT_VERSION && qt_version <= QT_VERSION)) {  
  125.             // 如果映射表中还未映射过关键字key(!previous), 或者之前映射到key的库的Qt版本号不合适时,  
  126.             // 都讲 key 映射到 library 。  
  127.                     d->keyMap[key] = library;  
  128.                     ++keyUsageCount;    // 映射计数加1  
  129.                 }  
  130.             }  
  131.           
  132.         // 如果映射计数大于0或者库library的元信息中的MetaData:Keys 字段为空(没有关键字),则将库library添加到库列表d->libraryList 中  
  133.             if (keyUsageCount || keys.isEmpty())  
  134.                 d->libraryList += library;    // 这里建立了 库列表(的一部分)  
  135.             else  
  136.                 library->release();  
  137.         }  
  138.     }  
  139. #else  
  140.     Q_D(QFactoryLoader);  
  141.     if (qt_debug_component()) {  
  142.         qDebug() << "QFactoryLoader::QFactoryLoader() ignoring" << d->iid  
  143.                  << "since plugins are disabled in static builds";  
  144.     }  
  145. #endif  

你可能感兴趣的:(Qt5的插件机制(2)--QxxxFactory类与QFactoryLoader类)