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>類型的指針。這個函數的功能一般都很easy,
                    // 其內部僅僅須要 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);    // 依據插件的keyword查找該插件所屬的庫在庫列表中的索引
    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> 類型的插件相關的keyword列表,
                    // keyword一般用於描寫敘述插件的名稱等屬性,這個keyword列表中的每一個
                    // 元素都相應一個實際的插件。如 QPlatformInputContextFactory::keys(...)
                    // 返回的就是輸入法插件的名稱列表。
    
    static Q<pluginType> * creat(...) ;    // 返回一個Q<pluginType>類型的指針(應用程序所需的插件)    
}


Q<pluginType>Factory::creat(...) 函數中,會通過檢測環境變量等手段來獲取到與對應的插件相關的keyword。然后再用該keyword
去調用 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(Higher-level API)和低級API(Lower-level API)兩種API能夠用來寫插件。
高級API的用法能夠在Qt源代碼中看到非常多實例。低級API的使用演示樣例在本系列文章的最后給出。


2. 假設不會寫插件的 .pro 文件,能夠在Qt Assistant 中搜索 qmake Manual , 這一頁里有非常多鏈接是與編寫project文件相關的,如
qmake Language 鏈接講 .pro 文件的語法,Variables 鏈接講.pro 文件里的變量(如QT、CONFIG、TEMPLATE等變量),
 Replace Functions 和 Replace Functions 鏈接講一些內建的函數(能夠在.pro文件里使用)





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM