Qt 插件学习

来源:互联网 发布:遥感影像数据如何访问 编辑:程序博客网 时间:2024/06/05 07:45

Qt 插件学习(一)

插件是什么
  • 注意:这儿暂时不考虑静态插件(潜意识中总觉得它根本就不算插件)。

插件是一个动态库(共享库)。动态库是一个独立的文件中的独立模块,可被多个程序访问。

先看动态库的两种用法

1. 程序链接时指明动态库

这时程序中包含相应的头文件,编译时指定头文件路径,对于qmake来说:

LIBS += -L/path1/path2/.../ -labcd
INCLUDEPATH += /p1/p2/.../

这样一来,程序启动时会自动加载需要的链接库。

2. 程序中动态加载动态库

运行过程中找到来查找某个动态库,加载并解析出其中的某个函数。 Qt提供了QLibrary这个类来封装各个平台下的差异。

插件是提供特定接口的动态库
  • 它是动态库
  • 它需要一个或几个特定的接口(这样程序才能感知它)
  • 它采用第二种加载方式
  • Qt 为插件提供了 QPluginLoader,比通用 QLibrary 好用
Qt插件位置

动态加载,那么程序怎么知道去哪儿加载插件呢?

很多人抱怨,装有Qt的机器上一切正常,发布后,jpeg等格式图片看不成,数据库无法连接,汉字乱码...

其实答案很简单:

QCoreApplication 的 libraryPaths() 中有一些路径,比如:

  • $QTDIR/plugins
  • 可执行程序所在文件夹

然后程序启动后,会去这些路径下的下列子目录

  • imagesformats

    中 找图片插件

    sqldrivers

    中 找数据库驱动插件

    codecs

    中 找字符的编解码插件

    ...

这就是为什么:将 jpeg4.dll 放到可执行程序所在文件夹的 imagesformats 中即可解决问题的原因。

除此之外,要设置插件路径,我们还可以修改:

QCoreApplication::libraryPaths()

通过 addLibraryPath()

QLibraryInfo::location(QLibraryInfo::PluginsPath)

通过 qt.conf 文件

QT_PLUGIN_PATH

环境变量

Manual 中对此有详细介绍。有关插件路径问题,请考虑 http://blog.csdn.net/dbzhang800/archive/2011/06/14/6543489.aspx

插件API

Qt 提供了两个层次的Api

Higher-level

用于扩展Qt自身的工程,比如前述的图片插件、编解码插件等等
前面的插件位置提到的主要是这个

Lower-level

用于扩展 应用程序的功能,比如 QtCreator 本身用了非常多的专有插件

higher-level

这部分,插件本身的api很简单,困难在功能实现上。比如实现一个 style 插件:

  • 派生 QStyle 或它的子类,实现自己的 Style 类(难点)
  • 派生 QStylePlugin,实现插件,在它内部创建 Style 的对象

这两个都没什么好说的,只是一个宏特别关键:

Q_EXPORT_PLUGIN2(PluginName, ClassName)

看它的源代码,也是一堆宏,贴出来也不好看

# define Q_EXPORT_PLUGIN2(PLUGIN, PLUGINCLASS) \
Q_PLUGIN_VERIFICATION_DATA \
Q_EXTERN_C Q_DECL_EXPORT \
const char * Q_STANDARD_CALL qt_plugin_query_verification_data() \
{ return qt_plugin_verification_data; } \
Q_EXTERN_C Q_DECL_EXPORT QT_PREPEND_NAMESPACE(QObject) * Q_STANDARD_CALL qt_plugin_instance() \
Q_PLUGIN_INSTANCE(PLUGINCLASS)

展开看一下:

static const char qt_plugin_verification_data[] = \
"pattern=QT_PLUGIN_VERIFICATION_DATA\n" \
"version=4.7.0\ndebug=false\nbuildkey=xxx";

extern "C" Q_DECL_EXPORT const char * qt_plugin_query_verification_data()
{
return qt_plugin_verification_data;
}

extern "C" Q_DECL_EXPORT qt_plugin_instance()
{
static QPointer<QObject> _instance;
if (!_instance)
_instance = new ClassName;
return _instance;
}

这样一来舒服多了,导出了两个函数:

  • 一个返回值是一个长长的字符串,用来校验差价和程序所用Qt版本什么的是否匹配
  • 另一个返回的是插件类的对象,这样通过该函数就可以使用插件了。
lower-level

这一部分和本文其他部分关系不是太紧密,而且内容可能比较多,还是单独出来准备"插件学习二"吧。

  • 不像图片插件、数据库插件等,Qt为他们提供了现成的接口。这儿我们必须实现自己的接口(别人也不清楚我们的需求,对吧)
静态插件
  • 这个东西怎么说呢,尽管静态编译过Qt,但基本没用过。所以在这静态插件上面应该更没有什么发言权。
  • 熟悉Qt静态编译的,应该对此更熟一些,因为这是插件都是静态的。
静态插件使用

这时它是一个静态库。静态库如何使用?不用多说了,肯定直接链接到程序中。于是我们需要:

  • 包含头文件
  • 指定链接库

对与Qt提供的 higher-level 插件,比如图片插件 jpeg,这意味着:

代码中,(使用宏,也就是使动态编译时它不起作用)

  • #include<QtPlugin>

  • Q_IMPORT_PLUGIN(qjpeg)

pro文件内,(指定要链接的库)

  • QTPLUGIN += qjpeg
静态插件用到的宏

当静态编译时,插件中的宏

Q_EXPORT_PLUGIN2(PLUGINNAME, ClassName)

展开为

qt_plugin_instance_PLUGINNAME()
{
static QPointer<QObject> _instance;
if (!_instance)
_instance = new ClassName;
return _instance;
}

同时,工程中的宏

Q_IMPORT_PLUGIN(PluginName)

展开为:

QObject *qt_plugin_instance_PLUGINNAME();
class StaticPLUGINNAMEPluginInstance
{
public:
StaticPLUGINNAMEPluginInstance()
{
qRegisterStaticPluginInstanceFunction(qt_plugin_instance_PLUGINNAME);
}
};
static StaticPLUGINNAMEPluginInstance staticPLUGINNAMEInstance;

这也很容易理解,Q_EXPORT_PLUGIN2(PLUGINNAME, ClassName) 中的第一个参数做什么用了。

参考
  • http://doc.qt.nokia.com/4.7/plugins-howto.html

  • http://doc.qt.nokia.com/4.7/deployment-plugins.html

Qt 插件学习(二)

20110614 更新插件路径问题:http://blog.csdn.net/dbzhang800/archive/2011/06/14/6543489.aspx

接前面的Qt插件学习(一),继续学习插件的 lower level api

这次,直接写个小例子吧:

接口

程序要能感知插件,需要程序和插件共同遵守某种规则。于是定义一个共同的接口

//mathinterface.h
#include <QtCore/QtPlugin>
class MathInterface
{
public:
virtual ~MathInterface() {}
virtual int math(int v) = 0;
};
Q_DECLARE_INTERFACE(MathInterface, "com.example.Plugin.MathInterface/0.1");

这儿用到的这个宏的定义在qobject.h中:

#define Q_DECLARE_INTERFACE(IFace, IId) \
template <> inline const char *qobject_interface_iid<IFace *>() \
{ return IId; } \
template <> inline IFace *qobject_cast<IFace *>(QObject *object) \
{ return reinterpret_cast<IFace *>((object ? object->qt_metacast(IId) : 0)); } \
template <> inline IFace *qobject_cast<IFace *>(const QObject *object) \
{ return reinterpret_cast<IFace *>((object ? const_cast<QObject *>(object)->qt_metacast(IId) : 0)); }

注意,这儿用到了

qt_metacast()稍后会看到这个函数在何处被定义。使用插件

//main.cpp
#include <QtCore>
#include "mathinterface.h"

int main(int argv, char *args[])
{
QCoreApplication app(argv, args);
QDir pluginsDir(qApp->applicationDirPath());
foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = pluginLoader.instance();
if (plugin) {
MathInterface *interface = qobject_cast<MathInterface *>(plugin);
if (interface) {
qDebug()<<interface->math(10);
}
}
}
return app.exec();
}

遍历一个路径(比如这儿的可执行程序所在目录),依次用 QPluginLoader 进行加载,如果加载成功且转换成接口指针成功,则调用接口的函数。

这儿用了一个 qobject_cast 进行类型转换。是不是很神奇:这儿转换的类型不是我们一直坚信的 QObject 或其子类。这用的是我们接口定义时的宏。

插件定义

头文件

//plugin1.h
#include <QtCore/QObject>
#include "mathinterface.h"

class Plugin1:public QObject, public MathInterface
{
Q_OBJECT
Q_INTERFACES(MathInterface)
public:
Plugin1(QObject *parent=NULL);
int math(int v);
};

cpp文件

//plugin1.cpp
#include "plugin1.h"
Plugin1::Plugin1(QObject *parent)
:QObject(parent)
{
}
int Plugin1::math(int v)
{
return 100*v;
}
Q_EXPORT_PLUGIN2(plugin1, Plugin1);

这儿出现的新的宏是:Q_INTERFACES(MathInterface)

这是个moc处理的宏,moc的结果中包含下面的代码:

void *Plugin1::qt_metacast(const char *_clname)
{
if (!_clname) return 0;
if (!strcmp(_clname, qt_meta_stringdata_Plugin1))
return static_cast<void*>(const_cast< Plugin1*>(this));
if (!strcmp(_clname, "MathInterface"))
return static_cast< MathInterface*>(const_cast< Plugin1*>(this));
if (!strcmp(_clname, "com.example.Plugin.MathInterface/0.1"))
return static_cast< MathInterface*>(const_cast< Plugin1*>(this));
return QObject::qt_metacast(_clname);
}

从这儿可以看出前面的qobject_cast是如何通过调用qt_metacast来起作用的。

pro文件

为了完整,看一下程序和插件分别对应的pro文件

  • project.pro
  • app/
    • app.pro
    • main.cpp
    • mathinterface.h
  • plugin1/
    • plugin1.pro
    • plugin1.h
    • plugin1.cpp

#app.pro
HEADERS = mathinterface.h
SOURCES = main.cpp
CONFIG += console
TARGET = main
DESTDIR = ../

#plugin1.pro
TEMPLATE = lib
CONFIG += plugin
INCLUDEPATH += ../app
HEADERS = plugin1.h
SOURCES = plugin1.cpp
DESTDIR = ../

其实Qt自带的Manual和例子讲得很清楚了,本文中不过提了一点用得到宏是如何工作的。

QPluginLoader

使用插件是通过QPluginLoader来完成的。

回想上一篇中提到 Q_EXPORT_PLUGIN2(PluginName, ClassName) 展开后的两个函数:

static const char qt_plugin_verification_data[] = \
"pattern=QT_PLUGIN_VERIFICATION_DATA\n" \
"version=4.7.0\ndebug=false\nbuildkey=xxx";

extern "C" Q_DECL_EXPORT const char * qt_plugin_query_verification_data()
{
return qt_plugin_verification_data;
}

extern "C" Q_DECL_EXPORT qt_plugin_instance()
{
static QPointer<QObject> _instance;
if (!_instance)
_instance = new ClassName;
return _instance;
}

QPluginLoader 正是借助这两个接口函数,来判断我们的插件是否有效的。看一下load的源码:

这儿的d是 QLibraryPrivate 的实例

bool QPluginLoader::load()
{
if (!d || d->fileName.isEmpty())
return false;
if (did_load)
return d->pHnd && d->instance;
if (!d->isPlugin())
return false;
did_load = true;
return d->loadPlugin();
}

两个接口函数是分别在 d->isPlugin() 和 d->loadPlugin() 中被使用的。

参考
  • http://doc.qt.nokia.com/4.7/tools-echoplugin.html

  • http://doc.qt.nokia.com/4.7/plugins-howto.html

  • http://doc.qt.nokia.com/4.7/qpluginloader.html