==========================================================
QT提供2个API来建立插件
1、高层API扩展QT库。例如定制的数据库驱动,图像格式,字符编码,custom styles
2、底层API扩展QT应用程序

例如,如果想写自定义QStyle的子类,并让应用程序动态加载,需要使用高层API函数

因为高层API在底层API基础上构造,两者需要注意一些问题。

如果需要提供给QT Designeder插件,请参考QtDesigner模型文档
===============
高层API
==============
利用继承特定的基类来实现插件,需要实现其中的一些函数,并增加一个宏

QT中有不少插件的基类可供使用,继承的插件默认保存在标准插件目录的子目录下,否则Qt会找不到。

设计一个style类名为MyStyle的插件
文件mystyleplugin.h:
class MyStylePlugin : public QStylePlugin
{
public:
QStringList keys() const;    //返回此插件能够使用的style名称列表。
QStyle *create(const QString &key);//根据传入的style名称,返回style。
};

mystyleplugin.cpp
#include "mystyleplugin.h"

QStringList MyStylePlugin::keys() const
{
return QStringList() << "MyStyle";
}

QStyle *MyStylePlugin::create(const QString &key)
{
if (key.toLower() == "mystyle")
return new MyStyle;
return 0;
}

Q_EXPORT_PLUGIN2(pnp_mystyleplugin, MyStylePlugin)

风格实现
文件mystyle.h:
class MyStyle : public QWindowsStyle
{
Q_OBJECT

public:
MyStyle() {};

void polish(QPalette &palette);
};
继承自QWindowsStyle
风格实现
void MyStyle::polish(QPalette &palette)
{
palette.setBrush(QPalette::Button, Qt::red);
}


(注意其中实现的大小写方式)。
实现数据库驱动,图像格式、文本编码和大多数其他插件类型时,一般不需要建立对象,Qt会找到他们并建立他们的对象。Style是一个特例,因为程序中科能会这样调用:
QApplication::setStyle(QStyleFactory::create("MyStyle"));

main.c文件:
int main(int argv, char *args[])
{
QApplication app(argv, args);//QT此时加载了插件
QApplication::setStyle(QStyleFactory::create("simplestyle"));

StyleWindow window;
window.resize(200, 50);
window.show();

return app.exec();
}

工程文件:
TEMPLATE    = lib
CONFIG     += plugin
HEADERS     = simplestyle.h \
simplestyleplugin.h
SOURCES     = simplestyle.cpp \
simplestyleplugin.cpp
TARGET      = simplestyleplugin

注意,插件需要设置TEMPLATE,因为我们需要的是共享库而不是执行程序。同时必须设置CONFIG。需要将此插件保存到style文件夹(应用程序所在的文件夹)。这样应用程序就能够检测得到。



========================================================================================================
底层API:没有详细研究
===============
不管是QT本身还是QT应用程序都可以通过插件扩展。这需要应用程序检测并通过QPluginLoader进行加载。因此,插件可以提供任意功能,而不仅限于上面说的插件。

插件扩展应用包括4个步骤
1、定义插件的接口集(实际上是只有虚函数的一个类)
2、Q_DECLARE_INTERFACE宏告诉meta-object系统此接口的存在。
3、利用QPluginLoader加载插件
4、使用qobject_cast()函数测试插件实现的借口

插件编码的4个步骤
1、继承QObject定义一个插件类,并定义插件需要的接口
2、使用Q_InterFaces()宏告诉meta-object系统接口的存在
3、使用Q_EXPORT_PLUGIN2()宏输出插件
4、编译工程

例如下面例子:
接口类:
class FilterInterface    //只有虚函数的类
{
public:
virtual ~FilterInterface() {}

virtual QStringList filters() const = 0;
virtual QImage filterImage(const QString &filter, const QImage &p_w_picpath,
QWidget *parent) = 0;
};

插件类,实现接口
#include
#include
#include

#include

class ExtraFiltersPlugin : public QObject, public FilterInterface
{
Q_OBJECT
Q_INTERFACES(FilterInterface)

public:
QStringList filters() const;
QImage filterImage(const QString &filter, const QImage &p_w_picpath,
QWidget *parent);
};
===========================
定位插件
===========================
QT应用程序自动加载插件,因为插件存在标准插件的子目录中
开发过程中,插件的目录在QTDIR/plugins中(QDIR是QT安装的目录)。如果希望应用程序使用或不使用标准插件,安装会得到希望安装插件的路径,并保存路径(例如应用程序使用QSettings在启动时读取它)
应用程序可以通过QCoreApplication:addLibraryPath是插件对于应用程序可见,注意,最终的路径不能改变。

如果希望插件可被加载,一种方法是在应用程序的所在目录的子目录下保存此插件。如果想发布QT自带的任何插件,需要拷贝plugins的子目录到应用程序的根目录下。(而不是包含插件的目录)

=================
静态插件
=================
一般的方法是将插件做成动态库和应用程序一块发布。插件动态检测和加载。

应用程序可以静态链接。假如编译的是静态的QT库,那么静态插件就是唯一的选择了。使用静态插件可以降低错误概率。但是缺点是修改插件需要重新编译整个应用程序。
QT提供一些静态的插件:

为了静态链接插件,需要在程序中使用Q_IMPORT_PLUGIN宏,需要在编译时使用QTPLUGIN参数。例如,在main.cpp中
#include
#include

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();
}
工程文件中

QTPLUGIN     += qjpeg \
qgif \
qkrcodecs

It is also possible to create your own static plugins, by following these steps:

Add CONFIG += static to your plugin's .pro file.
Use the Q_IMPORT_PLUGIN() macro in your application.
Link your application with your plugin library using LIBS in the .pro file.
See the Plug & Paint example and the associated Basic Tools plugin for details on how to do this.

Note: If you are not using qmake to build your application you need to make sure that the QT_STATICPLUGIN preprocessor macro is defined.  

(来自Qt文档)

Qt有两种与插件有关的API。一种用来扩展Qt本身的功能,如自定义数据库驱动,图像格式,文本编解码,自定义分格,等等,称为Higher-Level API。另一种用于应用程序的功能扩展,称为Lower-Level API。前一种是建立在后一种的基础之上的。这里讨论的是后一种,即用来扩展应用程序的Lower-level API。


让应用程序支持插件扩展的步骤:
  1. 定义一个接口集(只有纯虚函数的类),用来与插件交流。
  2. 用宏
Q_DECLARE_INTERFACE()将该接口告诉Qt元对象系统


 

 1  Q_DECLARE_INTERFACE(BrushInterface,"com.trolltech.PlugAndPaint.BrushInterface/1.0")

  3. 应用程序中用QPluginLoader来装载插件。
  4. 用宏qobject_cast()来确定一个插件是否实现了接口。

 1  QObject *obj = new QTimer;  
 2 QTimer *timer = qobject_cast(obj);



写一个插件的步骤:
  1. 声明插件类,该类从QObject和该插件希望实现的接口继承而来。
  2. 用宏Q_INTERFACES()将该接口告诉Qt元对象系统。


 

 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   };

  3. 用宏Q_EXPORT_PLUGIN2()导出插件。
 

 1  Q_EXPORT_PLUGIN2 ( PluginName, ClassName )

  4. 用适当的.pro文件构建插件。

 

下面的代码声明了一个接口类:

 1 class FilterInterface
 2 {
 3 public :
 4       virtual ~ FilterInterface() {}
 5       virtual QStringList filters() const = 0 ;
 6       virtual QImage filterImage( const QString & filter, const QImage & p_w_picpath, 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 & p_w_picpath,
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模式编译,要在工程中添加:

 
    

这能确保插件兼容应用程序中所用的库版本。

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中:

 
    

应用程序的.pro 文件中要用下列条目:

 
    

也可以创建自己的静态库,步骤如下:

  1. 在插件的 .pro 文件中添加 CONFIG += static
  2. 应用程序中用宏 Q_IMPORT_PLUGIN() 。
  3. 应用程序的.pro 文件中用 LIBS 将静态库链接进来。

参见示例 Plug & Paint 和相关的插件 Basic Tools 来获得详情。

The Plugin Cache

为了加速插件的装载和确认,装载插件时收集的信息被缓存到QSettings中。这包括插件是否被成功装载的信息,以使后面的装载操作不用再尝试装载无效的插件。但是,若一个插件的 last modified 时间戳被修改,插件的缓存条目是无效的并且插件会不管缓存条目中的值而被重新装载,同时缓存条目本身也会被新的值替代。

这也意味着每一次插件或任何依赖资源(如共享库)被更新之后时间戳也必须被更新,因为依赖资源可能影响一个插件装载的结果。

有时,开发插件时,需要从插件缓存中移除条目。因为Qt用QSettings来管理插件缓存,插件的位置是依赖于平台的;更多关于每一个平台的信息请参看the QSettings documentation

例如,Windows中这些条目存储在注册表中,每个插件的路径是以下面两个字串中的一个开始的:

 
    

Debugging Plugins

有许多问题可能影响到插件在应用程序中的正常运转。  许多与插件和应用程序的创建方法不同有关, 通常发生在不同的创建系统和过程中。

下表描述的是开发者创建插件时遇到的问题的常见原因:

Problem Cause Solution
应用程序打开插件时插件装载失败且无提示。Qt DesignerHelp|About Plugins对话框中显示插件库,但这里没有插件。 应用程序和插件是在不同模式下创建的 使用相同的创建信息;或者在他们的工程文件中的CONFIG变量中添加debug_and_release来用debug和release两种模式创建插件。
用有效的插件替代无效插件时装载失败 插件缓存中该插进的条目显示原来的插件不能被卸载,导致Qt忽略了替代 确保插件的时间戳被更新 或删除插件缓存中的条目

也可以用QT_DEBUG_PLUGINS环境变量来从Qt中获得尝试去装载的每一个插件的诊断信息。在应用程序的运行环境中把该变量的值设置为非零。

See also QPluginLoader, QLibrary, and Plug & Paint Example.