(來自Qt文檔)
Qt有兩種與插件有關的API。一種用來擴展Qt本身的功能,如自定義數據庫驅動,圖像格式,文本編解碼,自定義分格,等等,稱為Higher-Level API。另一種用于應用程序的功能擴展,稱為Lower-Level API。前一種是建立在后一種的基礎之上的。這里討論的是后一種,即用來擴展應用程序的Lower-level API。
讓應用程序支持插件擴展的步驟:
1. 定義一個接口集(只有純虛函數的類),用來與插件交流。
2. 用宏Q_DECLARE_INTERFACE()將該接口告訴Qt元對象系統。
3. 應用程序中用QPluginLoader來裝載插件。1 Q_DECLARE_INTERFACE(BrushInterface,"com.trolltech.PlugAndPaint.BrushInterface/1.0")
4. 用宏qobject_cast()來確定一個插件是否實現了接口。
1 QObject *obj = new QTimer;
2 QTimer *timer = qobject_cast<QTimer *>(obj);
寫一個插件的步驟:
1. 聲明插件類,該類從QObject和該插件希望實現的接口繼承而來。
2. 用宏Q_INTERFACES()將該接口告訴Qt元對象系統。
3. 用宏Q_EXPORT_PLUGIN2()導出插件。1 class BasicToolsPlugin : public QObject,
2 public BrushInterface,
3 public ShapeInterface,
4 public FilterInterface
5 {
6 Q_OBJECT
7 Q_INTERFACES(BrushInterface ShapeInterface FilterInterface)
8 public:
9 ...
10 };
4. 用適當的.pro文件構建插件。1 Q_EXPORT_PLUGIN2 ( PluginName, ClassName )
下面的代碼聲明了一個接口類:
1 class FilterInterface
2 {
3 public:
4 virtual ~FilterInterface() {}
5 virtual QStringList filters() const = 0;
6 virtual QImage filterImage(const QString &filter, const QImage &image, QWidget* parent)=0;
7 };
8
9 Q_DECLARE_INTERFACE(FilterInterface, "com.trolltech.PlugAndPaint.FilterInterface/1.0")
這里是實現該接口的插件類的定義:
1 #include <QObject>
2 #include <QStringList>
3 #include <QImage>
4
5 #include <plugandpaint/interfaces.h>
6
7 class ExtraFiltersPlugin : public QObject, public FilterInterface
8 {
9 Q_OBJECT
10 Q_INTERFACES(FilterInterface)
11
12 public:
13 QStringList filters() const;
14 QImage filterImage(const QString &filter, const QImage &image,
15 QWidget *parent);
16 };
示例 Plug & Paint 的文檔詳細解釋了這一過程。與Qt Designer有關的問題請看Creating Custom Widgets for Qt Designer 。 Echo Plugin Example 是一個關于如何實現擴展Qt應用程序的詳細示例。
Loading and Verifying Plugins Dynamically
裝載插件時。Qt庫有一些健全檢查來確定插件能否被裝載和使用。這就可以同時安裝多個版本和Qt庫配置。
- 與較高主版本和(或)次版本號的Qt庫鏈接的插件不能被主版本和(或)次版本號較低的庫裝載。
原理: 一個使用新版Qt庫的插件可能用了老版本沒有的新特征。Trolltech有一個只在次版本號升級時添加新功能和API的政策,這就是為什么該測試只看主次版本號,而不看補丁號。
- Qt庫和所有插件用一個聯編關鍵字來聯編。Qt庫中的聯編關鍵字被與插件中的聯編關鍵字對照,如果相符,插件就被裝載。如果聯編關鍵字不符,Qt庫就拒絕裝載該插件。
原理: 見下文對聯編關鍵字的解釋。
編譯插件來擴展應用程序時,確保插件和應用程序用同樣的配置這一點很重要。這意味著如果應用程序是release模式編譯的,那么插件也要是release模式。
若將Qt配置為debug和release模式都編譯,但只在release模式下編譯應用程序,就要確保你的插件也是在release模式下編譯的。缺省的,若Qt的debug編譯可用,插件就只在debug模式下編譯。要強制插件用release模式編譯,要在工程中添加:
CONFIG += release
這能確保插件兼容應用程序中所用的庫版本。
The Build Key
裝載插件時,Qt核對每一個插件的聯編關鍵字要和自己的匹配,以保證所裝載的是兼容的插件;任何不匹配的插件不會被裝載。
聯編關鍵字包含一下信息:
- Architecture, operating system and compiler.
原理: 在同一編譯器的不同版本并不產生二進制兼容代碼的場合,編譯器的版本也體現在聯編關鍵字里。
- Qt庫的配置。這個配置是庫中所缺少特性的列表,因為這些功能對應的API在該庫中不可用。
原理: 兩個同一版本的Qt庫的不同配置不是二進制兼容的。裝載插件的Qt庫使用這個(缺少的)特性列表來判斷插件是不是二進制兼容的。
注意: 也存在這種情況,插件可以使用在兩個不同配置里可用到的特性。但是,編寫插件的開發者需要知道,哪些特性在他們的插件和Qt的公用工具類中都在被使用。Qt庫在裝載插件時會需要復雜的特性與依賴性的查詢確認。這些需求給開發者添了一個不必要的負擔,也增加了裝載插件的系統開銷。為了減少開發時間,降低應用的運行時消耗,可以使用對聯編關鍵字的簡單字符串比較。
- 可選地,可以在配置腳本命令行指定一個附加的字符串Optionally, an extra string may be specified on the configure script command line.
原理: 在發布帶有應用程序的Qt庫的二進制時,這給開發者提供了一個編寫插件的辦法,這樣寫出來的插件只能被插件鏈接的那個庫所裝載。
為了調試可能需要關閉聯編關鍵字校驗功能,這可以通過將你運行應用程序的環境的環境變量QT_NO_PLUGIN_CHECK設置為非零來實現。
Static Plugins
插件能被靜態地鏈接到應用程序。如果你創建了Qt的靜態版本,這僅僅是用來包含Qt的預定義插件的一個選項。
當被作為靜態庫編譯時,Qt提供下面這些靜態插件:
| Plugin name | Type | Description |
|---|---|---|
| qtaccessiblecompatwidgets | Accessibility | Accessibility for Qt 3 support widgets |
| qtaccessiblewidgets | Accessibility | Accessibility for Qt widgets |
| qdecorationdefault | Decorations (Qtopia) | Default style |
| qdecorationwindows | Decorations (Qtopia) | Windows style |
| qgif | Image formats | GIF |
| qjpeg | Image formats | JPEG |
| qmng | Image formats | MNG |
| qimsw_multi | Input methods (Qtopia) | Input Method Switcher |
| qwstslibmousehandler | Mouse drivers (Qtopia) | tslib mouse |
| qgfxtransformed | Graphic drivers (Qtopia) | Transformed screen |
| qgfxvnc | Graphic drivers (Qtopia) | VNC |
| qscreenvfb | Graphic drivers (Qtopia) | Virtual frame buffer |
| qsqldb2 | SQL driver | IBM DB2 |
| qsqlibase | SQL driver | Borland InterBase |
| qsqlite | SQL driver | SQLite version 3 |
| qsqlite2 | SQL driver | SQLite version 2 |
| qsqlmysql | SQL driver | MySQL |
| qsqloci | SQL driver | Oracle (OCI) |
| qsqlodbc | SQL driver | Open Database Connectivity (ODBC) |
| qsqlpsql | SQL driver | PostgreSQL |
| qsqltds | SQL driver | Sybase Adaptive Server (TDS) |
| qcncodecs | Text codecs | Simplified Chinese (People's Republic of China) |
| qjpcodecs | Text codecs | Japanese |
| qkrcodecs | Text codecs | Korean |
| qtwcodecs | Text codecs | Traditional Chinese (Taiwan) |
要靜態鏈接這些插件,你的應用程序中要用到宏Q_IMPORT_PLUGIN() 并且要用QTPLUGIN將需要的插件添加到你的編譯中。例如,在main.cpp中:
#include <QApplication>
#include <QtPlugin>
Q_IMPORT_PLUGIN(qjpeg)
Q_IMPORT_PLUGIN(qgif)
Q_IMPORT_PLUGIN(qkrcodecs)
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
...
return app.exec();
}應用程序的.pro 文件中要用下列條目:
QTPLUGIN += qjpeg \
qgif \
qkrcodecs也可以創建自己的靜態庫,步驟如下:
- 在插件的 .pro 文件中添加 CONFIG += static 。
- 應用程序中用宏 Q_IMPORT_PLUGIN() 。
- 應用程序的.pro 文件中用 LIBS 將靜態庫鏈接進來。
參見示例 Plug & Paint 和相關的插件 Basic Tools 來獲得詳情。
The Plugin Cache
為了加速插件的裝載和確認,裝載插件時收集的信息被緩存到QSettings中。這包括插件是否被成功裝載的信息,以使后面的裝載操作不用再嘗試裝載無效的插件。但是,若一個插件的 last modified 時間戳被修改,插件的緩存條目是無效的并且插件會不管緩存條目中的值而被重新裝載,同時緩存條目本身也會被新的值替代。
這也意味著每一次插件或任何依賴資源(如共享庫)被更新之后時間戳也必須被更新,因為依賴資源可能影響一個插件裝載的結果。
有時,開發插件時,需要從插件緩存中移除條目。因為Qt用QSettings來管理插件緩存,插件的位置是依賴于平臺的;更多關于每一個平臺的信息請參看the QSettings documentation。
例如,Windows中這些條目存儲在注冊表中,每個插件的路徑是以下面兩個字串中的一個開始的:
HKEY_CURRENT_USER\Software\Trolltech\OrganizationDefaults\Qt Plugin Cache 4.2.debug HKEY_CURRENT_USER\Software\Trolltech\OrganizationDefaults\Qt Plugin Cache 4.2.false
Debugging Plugins
有許多問題可能影響到插件在應用程序中的正常運轉。 許多與插件和應用程序的創建方法不同有關, 通常發生在不同的創建系統和過程中。
下表描述的是開發者創建插件時遇到的問題的常見原因:
| Problem | Cause | Solution |
|---|---|---|
| 應用程序打開插件時插件裝載失敗且無提示。Qt Designer的Help|About Plugins對話框中顯示插件庫,但這里沒有插件。 | 應用程序和插件是在不同模式下創建的 | 使用相同的創建信息;或者在他們的工程文件中的CONFIG變量中添加debug_and_release來用debug和release兩種模式創建插件。 |
| 用有效的插件替代無效插件時裝載失敗 | 插件緩存中該插進的條目顯示原來的插件不能被卸載,導致Qt忽略了替代 | 確保插件的時間戳被更新 或刪除插件緩存中的條目 |
也可以用QT_DEBUG_PLUGINS環境變量來從Qt中獲得嘗試去裝載的每一個插件的診斷信息。在應用程序的運行環境中把該變量的值設置為非零。
See also QPluginLoader, QLibrary, and Plug & Paint Example.


