QLibrary 使用各平台提供的标准API从DLL和共享对象中解析C符号。在Unix中使用 dlopen()/dlsym() ,在Windows中使用 GetProcAddress。
插件基础 描述了编写插件的一些基本概念。
Qt的插件机制是为使用Qt的插件服务的 。它提供了一堆宏,可以帮助我们创建生成插件对象的C函数,并生成元信息(通过moc)以判断对象是否实现了接口。由于Qt的插件使用Qt,它也验证插件是否是用和编译应用程序本身的兼容的Qt编译的。
考虑用于本文的下列基本的Qt插件的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// toolinterface.h 中的 ToolInterface
class
ToolInterface
{
public
:
virtual
QString
toolName()
const
= 0;
};
Q_DECLARE_INTERFACE(ToolInterface,
"in.forwardbias.tool/1.0"
);
// hammer.h 中的 Hammer(我们的 Hammer 插件)
#include "toolinterface.h"
class
Hammer :
public
QObject
,
public
ToolInterface
{
Q_OBJECT
Q_INTERFACES(ToolInterface)
public
:
QString
toolName()
const
{
return
"hammer"
; }
};
Q_EXPORT_PLUGIN2(hammer, Hammer);
|
Q_DECLARE_INTERFACE 和 Q_INTERFACES 的作用在接下来的章节解释。
当 moc 运行于 hammer.h 代码时,它将检查 Q_INTERFACES。它为一个名为 qt_metacall – void *Hammer::qt_metacast(const char *iname) 的函数生成代码。这个 casting 函数的根据 iname 返回一个接口的指针。moc 也将确认你放于 Q_INTERFACES 的接口的名字是否确实被声明了。它通过检查头文件和查找Q_DECLARE_INTERFACE来实现这点。在我们这个例子中,toolinterface.h 文件内有一个Q_DECLARE_INTERFACE 。
粗糙的伪代码:
1
2
3
4
5
6
7
8
|
// 在 moc_hammer.cpp
void
*Hammer::qt_metacast(
const
char
*iname)
{
if
(
strcmp
(iname,
"Hammer"
) == 0)
return
this
;
if
(
strcmp
(iname,
"ToolInterface"
) == 0)
return
static_cast
<ToolInterface *>(
this
);
// .. additional comparisons if you had more than one interface in Hammer
if
(
strcmp
(iname,
"in.forwardbias.tool/1.0"
) == 0)
return
static_cast
<ToolInterface *>(
this
);
// also responds to the string in Q_DECLARE_INTERFACE
}
|
一个需要铭记的注意事项是,moc 不懂得接口的继承。举例来说,如果 ToolInterface 继承自 GenericInterface,它将不可能使用 qt_metacast 转换成 GenericInterface。moc 没有 C++ 语法的解析器,因此它不能在前面生成的 qt_metacast 代码中添加 GenericInterface。一种解决办法是在 hammer.h 中写为 Q_INTERFACES(ToolInterface:GenericInterface),“:” 指代派生。
Q_DECLARE_INTERFACE 是一个定义了使 qobject_cast<Tool *>(hammer)返回工具指针的帮助函数的一个宏。qobject_cast 只是一个模板函数,你可以认为 Q_DECLARE_INTERFACE 为接口提供了一个模板的实例化。这个宏自身只是展开成一个对前面moc生成的qt_metacast函数的一个调用。因此 Q_DECLARE_INTEFACE 定义了一个调用 object->qt_metacall(“in.forwardbias.tool/1.0”) 的模板函数的实例化 qobject_cast<Tool *>(object)
这是一个在共享对象中被导出的 C 函数。它看起来像这样:
1
2
3
4
|
// pluginName 没有被使用 (看下一节静态插件来理解它的用途)
#define Q_EXPORT_PLUGIN2(pluginName, PluginClass) \
extern
"C"
PluginClass *qt_plugin_instance() {
return
new
pluginClass; }
extern
"C"
const
char
*qt_plugin_verification_data() {
return
"pattern=QT_PLUGIN_VERIFICATION_DATA\nversion=4.5.3\ndebug=false\nbuildkey=x86_64 linux g++-4 full-config"
;
|
注意:qt_plugin_instance 实际中使用了单例,为了易于理解上面进行了简化。
Q_IMPORT_PLUGIN2 定义了创建一个该插件实例的C函数并包含其他额外的函数来返回插件编译时使用的Qt的配置信息。
QPluginLoader 使用作为 Q_EXPORT_PLUGIN2 的一部分被嵌入的校验数据(见上面的例子)来确认一个插件是否和应用程序兼容。在UNIX下一个有意思方面是,Qt将 mmap该库然后进行字符串的搜索(从文件的尾部,也即是反向搜索)而不是加载库然后解析函数。这样做的原因似乎是,它显然更快,而且可以避免加载不兼容的插件。
Qt 的各个不同部分可以使用插件扩展 – 编解码、样式、字体引擎等。对于给出的一个插件,必须将其转换(cast)到所有被支持的接口来确定这个插件究竟实现了什么。为了避免这种开销,Qt的定义了插件应当被放置的标准路径。举例来说,样式插件必须在 plugins/styles
当Qt构建在静态模式下时,插件也必须是静态的。为什么呢?由于Qt是静态的,它根本无法加载动态链接到Qt的插件。唯一的办法是为所有的插件生成静态库并在目标程序中链接所有的静态库。
当Qt是在静态模式下构建时,Q_EXPORT_PLUGIN2 宏扩展成一个C函数 qt_plugin_instance_##pluginName()。 pluginName有助于避免使用多个插件时的名称冲突 —— 记住这是静态链接时的代码。
Q_EXPORT_PLUGIN2 生成注册静态插件的代码。另外两点需要注意:
1. 必须有人“注册(register)“这个插件。当静态构建Qt时,开发者决定哪些插件随程序发布。有人需要将这些被选择的插件需要到Qt系统。这是通过使用Q_IMPORT_PLUGIN(pluginName)实现的。它所做的是创建一个全局静态对象,其构造函数使用 qRegisterStaticPluginInstanceFunction 来注册插件的qt_plugin_instance_##pluginName。这样以来,Qt现在能知道这个插件的存在并且知道如何创建插件,但它不知道插件实现了什么! Qt只能通过遍历每个插件对象并尝试转换到每一个标准接口。
2. 该插件本身是静态库。当我们的应用程序被链接时,我们需要链接这些静态库。这是通过. pro文件的 QTPLUGIN+=pluginName(这只是简单添加了 -l<plugin>.a 的链接选项) 实现的。
1. 多个插件可以共存在一个单一的 DLL/.so中么? 你可以将多个 相同 类型的插件放在一个DLL中但不能将不同类型的的插件放于一个单一的 .so 中。比如:你可以将两个图片插件放于同一个.so但是你不能将一个图片插件和一个字体引擎插件放于同一个.so文件。
2. 动态 Qt 可以加载静态插件么?不可以。
3. 静态 Qt 可以加载动态插件么?不可以。