Qt5的插件機制(1)--Qt 框架中的插件載入機制概述

概述

Qt的源代碼中經過 Q<pluginType>Factory、Q<pluginType>Plugin 和 Q<pluginType> 這三個類實現了Qt的插件載入機制,linux

這個機制可用於載入特定種類的插件。

比方經過 QPlatformIntegrationFactory\QPlatformIntegrationPlugin\QPlatformIntegration
三個類可以實現平臺類QPA插件(PlatformIntegration)的載入,經過QPlatformInputContextFactory\QPlatformInputContextPlugin\
QPlatformInputContext三個類可以實現輸入法類插件(InputContext)的載入。

如下自底向上介紹Qt的插件載入機制
app


實現插件: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 類打交道就能夠。post

比方。在Qt應用程序初始化過程當中,它發現本身
ui

需要一個輸入法插件了,就會直接調用 QPlatformInputContextFactory::creat(...) 來生產一個輸入法類插件。而不用再管這個插件載入過程當中的細節。spa

返回的這個輸入法類插件到底是什麼。是ibus? 仍是fcitx?插件

這些全然由用戶經過環境變量QT_IM_MODULE指定,指針

 QPlatformInputContextFactory::create()方法中會去檢測這個環境變量。code




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

附 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文件裏使用)

相關文章
相關標籤/搜索