概述
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文件里使用)