• 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
    类也都是类似的。


    class Q_GUI_EXPORT QPlatformInputContextFactory
    {
    public:
        static QStringList keys();        // 仅仅有几个静态的成员函数
        static QPlatformInputContext *create(const QString &key);
        static QPlatformInputContext *create();
    };
    
    



    当中,keys() 方法用于获得全部同类插件的keyword。对于QPlatformInputContextFactory类,它的keys()方法自然就是获得全部输入法类插件。比方ibusfcitx等插件的keyword。
    QStringList QPlatformInputContextFactory::keys()
    {
    #if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)
        return loader()->keyMap().values();
    #else
        return QStringList();
    #endif
    }
    

    获取keyword的这段代码非常短。但我们立即就有个疑问,代码中出现的 loader() 是哪儿来的?它的返回值是什么?
    我最初读到这段代码时也一致纠结于此,由于哪里都找不到这个 loader() 的定义,后来才注意到,在这个源文件的
    开头。有一句
    #if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)
    Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader,	// <--- loader就在这里定义
        (QPlatformInputContextFactoryInterface_iid, QLatin1String("/platforminputcontexts"), Qt::CaseInsensitive))
    #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 对象相应的全部插件的keyword获取到组成一个列表。本文后面会介绍QFactoryLoader类。



    QPlatformInputContextFactory 有两个 create 方法,当中一个是指定了keyword,还有一个则自己搜索keyword。



    QPlatformInputContext *QPlatformInputContextFactory::create(const QString& key)
    {
        // 指定了keyword key 
        QStringList paramList = key.split(QLatin1Char(':'));    // 将keyword按分隔符分离成一个或多个參数,分隔符是冒号
        const QString platform = paramList.takeFirst().toLower();    // 获取key中分离的第一个參数。作为载入插件时用的keyword
    
    #if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)
        if (QPlatformInputContext *ret = qLoadPlugin1<QPlatformInputContext, QPlatformInputContextPlugin>(loader(), platform, paramList))
            return ret;
    #endif
        return 0;
    }
    
    
    
    QPlatformInputContext *QPlatformInputContextFactory::create()
    {
        // 未指定keyword。则自己搜索keyword。
        QPlatformInputContext *ic = 0;
    
        // 先从环境变量中搜索keyword
        QString icString = QString::fromLatin1(qgetenv("QT_IM_MODULE"));
    
        if (icString == QLatin1String("none"))
            return 0;
    
        ic = create(icString);
        if (ic && ic->isValid())
            return ic;
    
        delete ic;
        ic = 0;
    
        // 假设环境变量中找不到合适的keyword,则从 keys() 返回的keyword列表中一个一个试
        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;
    }
    


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

    QFactoryLoader 类:插件载入者


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

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

    class QFactoryLoaderPrivate : public QObjectPrivate
    {
        Q_DECLARE_PUBLIC(QFactoryLoader)
    public:
        QFactoryLoaderPrivate(){}    // 构造函数什么也不做
        ~QFactoryLoaderPrivate();    // 析构函数中卸载并释放插件/库列表中全部的库
                    /*    QFactoryLoaderPrivate::~QFactoryLoaderPrivate()
                        {
                            for (int i = 0; i < libraryList.count(); ++i) {
                            QLibraryPrivate *library = libraryList.at(i);
                            library->unload();
                            library->release();
                            }
                        }    */
    
        mutable QMutex mutex;
        QByteArray iid;        // 当前对象相应的插件集的 IID
        QList<QLibraryPrivate*> libraryList;    // 插件/库 列表
        QMap<QString,QLibraryPrivate*> keyMap;    // 插件/库 到 keyword的映射表
        QString suffix;                // 当前对象相应的插件集中全部插件/库相应的库文件(.so文件)路径的后缀(最低一级文件夹的名字)
        Qt::CaseSensitivity cs;        // 匹配keyword的策略。是精确匹配还是粗略匹配,一般都选粗略匹配
        QStringList loadedPaths;    // 全部已经载入过的库路径
    
        void unloadPath(const QString &path);
    };
    

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




    QFactoryLoader类的定义例如以下。



    class Q_CORE_EXPORT QFactoryLoader : public QObject
    {
        Q_OBJECT
        Q_DECLARE_PRIVATE(QFactoryLoader)
    
    public:
        explicit QFactoryLoader(const char *iid,
                       const QString &suffix = QString(),
                       Qt::CaseSensitivity = Qt::CaseSensitive);    
                // 构造函数,先设置插件集的 IID、插件路径后缀和keyword匹配策略,
                // 再调用update()生成插件列表和插件到keyword的映射表,并将当前的
                // QFactoryLoader对象加入到全局的loader列表qt_factory_loaders中 。
    
        ~QFactoryLoader();        // 析构。将当前的QFactoryLoader对象从全局的loader列表qt_factory_loaders中移除
    
        QList<QJsonObject> metaData() const;    // 返回d->libraryList(库列表)中每一个库的元信息组成的列表(列表的还加上了静态库的元信息)
        QObject *instance(int index) const;        // 返回d->libraryList(库列表)第index个插件的实例
    
    #if defined(Q_OS_UNIX) && !defined (Q_OS_MAC)
        QLibraryPrivate *library(const QString &key) const;    // 返回映射表中keywordkey相应的库
    #endif
    
        QMultiMap<int, QString> keyMap() const;    // 返回d->libraryList(库列表)中全部库的keyword映射表:每一个库有
                            // 一个int型索引(就是该库在库列表中的索引)和若干个QString
                            // 类型的keyword,这个函数的返回值是QMultiMap类型的一个“一对多”的映射表,一个
                            // 库(索引)相应若干个keyword。
    
        int indexOf(const QString &needle) const;    // 返回keywordneedle相应的库在库列表中的索引
    
        void update();    // 主要功能就是更新 库列表(d->libraryList) 和 keyword到库的映射表(d->keyMap), 一般在应用程序的库载入路径发生变化时才需调用
    
        static void refreshAll();    // 这是一个静态函数。用于将全局loader列表(qt_factory_loaders)中的全部QFactoryLoader对象都update() 一下
    };
    


    通过上面的凝视我们已经知道,库列表(d->libraryList) 和 keyword到库的映射表(d->keyMap)都是在构造函数中通过调用update()生成的。
    这两个表差点儿就是这个类的核心,以下我们就看看这两个表是怎样生成的。

    void QFactoryLoader::update()
    {
    #ifdef QT_SHARED
        Q_D(QFactoryLoader);
        QStringList paths = QCoreApplication::libraryPaths();    // 获取应用程序的库载入路径
    
        // 第一层循环, 開始遍历全部的库路径
        for (int i = 0; i < paths.count(); ++i) {
    
            const QString &pluginDir = paths.at(i);        // 获取第 i 个库路径
    
            // Already loaded, skip it... 假设当前库路径已经记录在了 d->loadedPaths 中,则跳过这个库路径。
            if (d->loadedPaths.contains(pluginDir))
                continue;
            d->loadedPaths << pluginDir;    // 将当前库路径加入到 d->loadedPaths 中。

    这样能够防止反复载入一个库路径 QString path = pluginDir + d->suffix; // 这个库路径加上当前QFactoryLoader对象相应的插件集的路径后缀。得到插件集的路径path(假设存在) if (qt_debug_component()) qDebug() << "QFactoryLoader::QFactoryLoader() checking directory path" << path << "..."; if (!QDir(path).exists(QLatin1String("."))) // 检測插件集路径path是否存在,不存在则跳过当前的库路径 continue; // 假设插件集路径path存在。接着往下执行 QStringList plugins = QDir(path).entryList(QDir::Files); // 列出当前插件集路径path中的全部文件名称。存入plugins列表中 QLibraryPrivate *library = 0; #ifdef Q_OS_MAC // Loading both the debug and release version of the cocoa plugins causes the objective-c runtime // to print "duplicate class definitions" warnings. Detect if QFactoryLoader is about to load both, // skip one of them (below). // // ### FIXME find a proper solution // const bool isLoadingDebugAndReleaseCocoa = plugins.contains(QStringLiteral("libqcocoa_debug.dylib")) && plugins.contains(QStringLiteral("libqcocoa.dylib")); #endif // 第二层循环。開始遍历插件集路径path中的全部文件 for (int j = 0; j < plugins.count(); ++j) { QString fileName = QDir::cleanPath(path + QLatin1Char('/') + plugins.at(j)); // 获取第 j 个文件的文件名称 #ifdef Q_OS_MAC if (isLoadingDebugAndReleaseCocoa) { #ifdef QT_DEBUG if (fileName.contains(QStringLiteral("libqcocoa.dylib"))) continue; // Skip release plugin in debug mode #else if (fileName.contains(QStringLiteral("libqcocoa_debug.dylib"))) continue; // Skip debug plugin in release mode #endif } #endif if (qt_debug_component()) { qDebug() << "QFactoryLoader::QFactoryLoader() looking at" << fileName; } // 尝试依据文件名称fileName创建库library (库用QLibraryPrivate类的对象表示) // 疑问: 假设插件集路径中包括有非库的普通文件(比方文本文件、图片),也要为他们生成一个 library ? 所以不应该在插件集路径中放非库的文件? library = QLibraryPrivate::findOrCreate(QFileInfo(fileName).canonicalFilePath()); if (!library->isPlugin()) { // 推断该库(library)是不是插件,假设不是插件。则跳过当前的库 if (qt_debug_component()) { qDebug() << library->errorString; qDebug() << " not a plugin"; } library->release();// 释放该库 continue; } // 假设该库(library)是插件。则继续往下执行 QStringList keys; bool metaDataOk = false; QString iid = library->metaData.value(QLatin1String("IID")).toString(); if (iid == QLatin1String(d->iid.constData(), d->iid.size())) { // 假设插件library的IID与当前QFactoryLoader对象相应的插件集的IID同样。则设置metaDataOk标志。并 // 读取该插件library的元信息中的 MetaData:Keys 字段,这个字段是个JSON数组类型, // 存储了若干字符串类型的keyword,将这些keyword天价到字符串列表 keys 中。 QJsonObject object = library->metaData.value(QLatin1String("MetaData")).toObject(); metaDataOk = true; // 设置metaDataOk标志 QJsonArray k = object.value(QLatin1String("Keys")).toArray(); for (int i = 0; i < k.size(); ++i) keys += d->cs ?

    k.at(i).toString() : k.at(i).toString().toLower(); } if (qt_debug_component()) qDebug() << "Got keys from plugin meta data" << keys; if (!metaDataOk) { // 假设metaDataOk标志未被设置。说明IID不匹配,跳过library这个插件 library->release(); continue; } int keyUsageCount = 0; // 映射计数,对映射到 library 上的keyword计数 // 第三层小循环,遍历library元信息中Keys字段存储的全部keyword // 这个循环建立了 库到keyword的映射表(的一部分) for (int k = 0; k < keys.count(); ++k) { // first come first serve, unless the first // library was built with a future Qt version, // whereas the new one has a Qt version that fits // better const QString &key = keys.at(k); // 获取第k个keywordkey // 假设映射表已经映射过 key 了。则获取其之前映射的那个库previous及其所用的Qt版本prev_qt_version, // 跟当前库library的Qt版本qt_version进行比較。假设prev_qt_version高于当前的QT版本而且qt_version // 不大于当前的QT版本,则将keyword key 又一次映射到 library 。 // 假设映射表中还未映射过keywordkey,则直接将其映射到 library QLibraryPrivate *previous = d->keyMap.value(key); int prev_qt_version = 0; if (previous) { prev_qt_version = (int)previous->metaData.value(QLatin1String("version")).toDouble(); } int qt_version = (int)library->metaData.value(QLatin1String("version")).toDouble(); if (!previous || (prev_qt_version > QT_VERSION && qt_version <= QT_VERSION)) { // 假设映射表中还未映射过keywordkey(!previous), 或者之前映射到key的库的Qt版本不合适时, // 都讲 key 映射到 library 。 d->keyMap[key] = library; ++keyUsageCount; // 映射计数加1 } } // 假设映射计数大于0或者库library的元信息中的MetaData:Keys 字段为空(没有keyword),则将库library加入到库列表d->libraryList 中 if (keyUsageCount || keys.isEmpty()) d->libraryList += library; // 这里建立了 库列表(的一部分) else library->release(); } } #else Q_D(QFactoryLoader); if (qt_debug_component()) { qDebug() << "QFactoryLoader::QFactoryLoader() ignoring" << d->iid << "since plugins are disabled in static builds"; } #endif }


    版权声明:本文博客原创文章,欢迎转载(本文强调禁止转载例外)。但请注明出处。如培训和其他商业用途,请联系博主,并支付了许可费的一部分

  • 相关阅读:
    「枫桥夜泊」一诗
    走遍亚洲 —— 泰国
    走遍亚洲 —— 泰国
    暴露年龄
    暴露年龄
    插入排序(insertion sort)
    开机黑屏 仅仅显示鼠标 电脑黑屏 仅仅有鼠标 移动 [已成功解决]
    OpenCV For iOS 1:&#160;连接OpenCV 3.0
    插入排序
    [hadoop系列]Pig的安装和简单演示样例
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/4733718.html
Copyright © 2020-2023  润新知