Qt中为我们提供了两种开发插件的方式。一种是使用High-Level API接口,一种是使用Low-Level API接口。所谓High-Level API 是指通过继承Qt为我们提供的特定的插件基类,然后实现一些虚函数、添加需要的宏即可。该种插件开发方式主要是用来扩展Qt库本身的功能,比如自定义数据库驱动、图片格式、文本编码、自定义样式等。而我们为自己的应用程序编写插件来扩展其功能时主要使用第二种方式,即Low-Level API 的方式,该方式不仅能扩展我们自己的应用程序,同样也能像High-Level API 那样用来扩展Qt本身的功能。使用这种方式,我们可以将我们需要扩展的功能写成一个 接口,然后让一个插件类去实现这个接口的功能,再使用Qt提供的用于插件开发的宏,按Qt要求的格式对插件进行声明,之后我们就可以在应用程序中使用QPluginLoader 来动态的加载该插件,从而完成应用程序功能的扩展。由于我们平时主要使用插件来扩展我们自己开发的程序,所以今天主要讲解一下使用Low-Level API开发插件的方式。至于High-Level API 方式,有需要的同学可以自行研读Qt的帮助文档和相关Demo。

想要让Qt编写的应用程序支持插件扩展,需要进行一下步骤:

1.定义一系列的接口,应用程序就是使用这些接口与插件进行功能交互的。(标准c++中没有接口的概念,所以此处的接口指只有纯虚函数的类)。

2.使用 Q_DECLARE_INTERFACE() 宏将这个接口的有关信息告诉Qt的元对象系统。

3.在应用程序中使用QPluginLoader 加载这个插件。

4.使用qobject_cast() 函数检测该插件是否实现了特定的接口。

有了应用程序声明的接口,我们还需要编写我们的插件来真正的实现接口所声明的功能,步骤如下:

1.声明一个插件类,让该类继承QObject 和 应用程序所提供的那个接口。

2.使用Q_INTERFACE() 宏告诉Qt元对象系统这个插件实现了哪些接口。

3.使用Q_PLUGIN_METEDATA() 宏导出这个插件。

4.在.pro 文件的进行相关配置,然后编译该插件。

上面说到使用Low-Level API接口开发插件所用到的几个宏定义,下面我们再来详细的看下每个宏的具体含义。

Q_DECLARE_INTERFACE(ClassName, Identifier):这个宏将一个给定的字符串标识符和ClassName所表示的接口相关联,其中Identifier必须唯一。例如:

#define BrushInterface_iid "org.qt-project.Qt.Examples.PlugAndPaint.BrushInterface"

Q_DECLARE_INTERFACE(BrushInterface, BrushInterface_iid)
        这个宏通常直接在接口所在的头文件中使用。还有,如果你的接口声明在了一个名称空间中,那么你要确保这个宏的使用位于名称空间外面。例如:

namespace Foo
{
struct MyInterface { ... };
}

Q_DECLARE_INTERFACE(Foo::MyInterface, "org.examples.MyInterface")
       Q_IMPORT_PLUGIN(PluginName): 这个宏向应用程序中导入名字为PluginName的插件,这个名字对应于Q_PLUGIN_METADATA() 所在类的类名。这个宏主要用来导入静态插件。
       Q_PLUGIN_METADATA(IID ... FILE ...) :这个宏用来声明插件的元数据信息。需要传入被实现接口的IID,和一个保护该插件元数据信息的json文件。注意,这个宏所在的class必须是可默认构造的;另外,FILE是可选的,若传入了一个json文件,则要确保编译系统能找到这个的文件,不然,moc(meta-object compiler) 会因为找不到该文件而失败退出。

刚才讲 Q_IMPORT_PLUGIN 时,提到了静态插件,相对于的也就有动态插件,并且我们使用最多的就是动态插件。下面分别通过一个例子来学习。
       动态插件 本质上仍然是一个dll,只不过我们在编写时根据Qt的要求将其配置成了插件,这样我们在使用时就可以通过QPluginLoader 来直接加载该dll,并调用其中的函数;并且,在定义插件时不需要写一堆的函数导出声明。下面,为了便于测试,我们在QtCreator 中新建一个子目录项目(用于包含其他项目的项目,类似于vs的解决方案)并且添加两个项目,一个是dll项目,一个是测试项目。步骤如下:

启动QtCreator,点击文件->新建文件或项目,选择其他项目->子目录项目

输入工程名即可,建立好后,如下:

此时项目为空,因为没有添加子项目。

在工程上 右键->新的子项目,先添加一个测试插件的项目test,如下

选择 QWidget 作为我们窗口的基类,如下:

同理,在DynamicPlugin上点右键->新的子项目,在此我选择一个空的qmake项目作为我们的插件项目,如下:

最终的项目结构如下:

然后,在test工程上右键->添加新文件,添加一个c++头文件interface.h,即我们的接口文件,一会就让我们的插件类实现这个接口。

该文件的内容如下:

#ifndef INTERFACE_H
#define INTERFACE_H

#include <QWidget>

class PluginInterface
{
public:
virtual ~PluginInterface() {}
virtual void SayHello(QWidget *parent) = 0;
};

#define pluginInterface_iid "io.qt.dynamicplugin"
Q_DECLARE_INTERFACE(PluginInterface, pluginInterface_iid)

#endif // INTERFACE_H
在此,为简单起见,我们只定义了一个SayHello() 纯虚函数,并使用Q_DECLARE_INTERFACE宏向Qt元对象系统声明了这个接口。

然后,在plugin工程上点右键->添加新文件->c++类,新建一个plugin类,让其继承QObject和我们自定义的接口,并实现SayHello() 纯虚函数。plugin.h内容如下:

#ifndef PLUGIN_H
#define PLUGIN_H

#include <QObject>
#include "../test/interface.h"

class plugin : public QObject, PluginInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID pluginInterface_iid FILE "plugin.json")
Q_INTERFACES(PluginInterface)
public:
void SayHello(QWidget *parent) Q_DECL_OVERRIDE;
};

#endif // PLUGIN_H
在此,我们同时使用相关宏向Qt元对象系统声明了该插件的相关信息。当然我们还要新建一个json文件,目前我们只想在plugin.json中写一个表示json格式的{} 即可。其实现文件如下:

void Plugin::SayHello(QWidget *parent)
{
QMessageBox::information(parent, "Plugin", "Hello, I'm dynamically loaded.");
}
为简单起见,我在此只弹出一个消息框。
最后也是最重要的一步,就是通过.pro文件,将该项目配置成动态插件,如下:

QT += widgets
TEMPLATE = lib
CONFIG += plugin

HEADERS += \
plugin.h

SOURCES += \
plugin.cpp

DISTFILES += \
plugin.json
其中,TEMPLATE指明这是一个dll工程,不是一个exe工程;config就是用类配置该工程为插件的。
构建该工程,即可在磁盘上生成该插件对应的dll。

接下来,我们在test工程中测试该插件。首先,在test工程的窗口上放一个按钮,并为该按钮关联一个槽函数。所实现的功能就是当点击按钮时,加载插件并调用SayHello() 弹出一个对话框。槽函数内容如下:

void Widget::OnClick()
{
PluginInterface *interface = nullptr;
QPluginLoader pluginLoader("plugin.dll");
QObject *plugin = pluginLoader.instance();
if(plugin)
{
interface = qobject_cast<PluginInterface*>(plugin);
if(interface)
{
interface->SayHello(this);
}
}
}
其中我们先定义了一个插件接口的指针,然后使用QPluginLoader 动态加载我们刚才生成的插件(若不在当前文件夹 下,需指明具体路径),在通过instance() 函数生成一个插件指针,若生成成功,在尝试将该指针转成我们实际需要的插件类型,然后调用插件的SayHello() 函数,弹出对话框。运行如下:

至此,动态插件的开发实例就完成了。

静态插件

上面我们开发动态插件时说过,动态插件其实也是一个dll文件,同理,静态插件其实也就是一个lib文件。所以,我们还以上面的例子来说明。仿照上面的过程,新建一个StaticPlugin的子目录工程,并新建好相关文件。然后,只需要修改三个地方即可实现静态插件的开发。

1.修改plugin工程的pro文件,在config后面添加static配置,即:CONFIG += plugin static

2.修改test工程的pro文件,添加 LIBS += ./libplugin.a,即为test工程引入静态插件所对应的.a文件(gcc)或.lib文件(vs)。若文件不在当前目录下,则需指定具体路径。

3.在main() 函数前添加 Q_IMPORT_PLUGIN(Plugin),即导入静态插件。

其使用方式如下:

void Widget::OnClick()
{
    PluginInterface *interface = nullptr;
    foreach (QObject *plugin, QPluginLoader::staticInstances())
    {
        interface = qobject_cast<PluginInterface*>(plugin);
        if(interface)
        {
            interface->SayHello(this);
        }
    }
}
通过QPluginLoader的静态方法staticInstances()使用加载到当前工程的所有静态插件。我们只需通过遍历,找到我们所需要的特定类型的插件即可。测试结果如下:

---------------------
作者:求道玉
来源:CSDN
原文:https://blog.csdn.net/Amnes1a/article/details/62223210
版权声明:本文为博主原创文章,转载请附上博文链接!

Qt插件开发入门(两种方法:High-Level API接口,Low-Level API接口)的更多相关文章

  1. jQuery插件开发的两种方法及$.fn.extend的详解(转)

    jQuery插件开发的两种方法及$.fn.extend的详解 jQuery插件开发分为两种:1 类级别.2 对象级别,下面为大家详细介绍下   jQuery插件开发分为两种: 1 类级别 类级别你可以 ...

  2. jQuery插件开发的两种方法及$.fn.extend的详解

    jQuery插件开发分为两种: 1 类级别 类级别你可以理解为拓展jquery类,最明显的例子是$.ajax(...),相当于静态方法. 开发扩展其方法时使用$.extend方法,即jQuery.ex ...

  3. Qt连接数据库的两种方法

    我曾经想过,无论在哪个平台下开发,都不要再接触SQL Server了,但显然不行.我们是来看世界的,不是来改变世界的,想通就好. 前两天,尝试了一下Qt下远程访问数据库.在macOS下,用Qt 5.1 ...

  4. Qt中的布局浅析与弹簧的使用,以及Qt居中的两种方法

    1. 布局 为什么要布局: 布局之后窗口的排列是有序的 布局之后窗口的大小发生变化, 控件的大小也会对应变化 如果不对控件布局, 窗口显示出来之后有些控件的看不到的 布局是可以嵌套使用 常用的布局方式 ...

  5. vue插件开发的两种方法:以通知插件toastr为例

    方法一: 1.写插件: 在 src 文件夹下面建 lib 文件夹用于存放插件,lib 文件夹下再建toastr文件夹,在toastr文件夹下新建 toastr.js 和 toastr.vue两个文件. ...

  6. jQuery插件开发的两种方法

    1 类级别 类级别你可以理解为拓展jquery类,最明显的例子是$.ajax(...),相当于静态方法. 开发扩展其方法时使用$.extend方法,即jQuery.extend(object); $. ...

  7. Qt 之 设置窗口边框的圆角(使用QSS和PaintEvent两种方法)

    Qt在设置窗口边框圆角时有两种方式,一种是设置样式,另一种是在paintEvent事件中绘制窗口.下面分别叙述用这两种方式来实现窗口边框圆角的效果. 一.使用setStyleSheet方法 this- ...

  8. [转]Qt中定时器使用的两种方法

    Qt中定时器的使用有两种方法,一种是使用QObject类提供的定时器,还有一种就是使用QTimer类. 其精确度一般依赖于操作系统和硬件,但一般支持20ms.下面将分别介绍两种方法来使用定时器. 方法 ...

  9. Qt中显示图像的两种方法

    博客转载自:https://blog.csdn.net/lg1259156776/article/details/52325361 在Qt中处理图片一般都要用到QImage类,但是QImage的对象不 ...

  10. QT中获取选中的radioButton的两种方法(动态取得控件的objectName之后,对名字进行比较)

    QT中获取选中的radioButton的两种方法   QT中要获取radioButton组中被选中的那个按钮,可以采用两种如下两种办法进行: 方法一:采用对象名称进行获取 代码: 1 QRadioBu ...

随机推荐

  1. zabbix监控php-fpm

    1.启用php-fpm的状态功能 [root@web01 ~]# vim /etc/php-fpm.d/www.conf 121 pm.status_path = /php_status [root@ ...

  2. 【OpenGL 学习笔记01】HelloWorld演示样例

    <<OpenGL Programming Guide>>这本书是看了忘,忘了又看,赶脚还是把笔记做一做心里比較踏实,哈哈. 我的主题是,好记性不如烂笔头. ========== ...

  3. 虚拟机只有IPv6,没有ipv4

    1.修改配置文件 vi /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 BOOTPROTO=none HWADDR=00:0C:29:20: ...

  4. Android_编程规范与经常使用技巧

    一.Android编码规范 1.java代码中不出现中文,最多凝视中能够出现中文 2.局部变量命名.静态成员变量命名 仅仅能包括字母,单词首字母出第一个外.都为大写,其它字母都为小写 3.常量命名 仅 ...

  5. 【Excle数据透视表】如何在数据透视表中使用合并单元格标志

    先有数据透视表如下: 现在看着这个格式不舒服,我们希望调整成如下这种样式 步骤 单击数据透视表任意单元格→右键→数据透视表选项→布局和格式→合并且居中排列带标签的单元格 注意:如果数据透视表报表布局不 ...

  6. Oracle LOB字段判空

    dbms_lob.getlength() dbms_lob.getlength(null) 会报错--- Oracle 默认为clob字段插入empty_clob()

  7. 转载(Asp.net Core 中试使用ZKWeb.System.Drawing)

    完美 原文Link: https://www.yanning.wang/archives/644.html 记录下做备份. 很少用Linux服务器. 这下可给整的够呛, 特别是按照官网竟然还不行, 所 ...

  8. shader之旅-7-平面阴影(planar shadow)

    根据<real-time shadow>这本书第二章中的推导,实现了最简单的阴影技术. planar shadow通过一个投影矩阵将被灯光照射的物体的顶点沿着光线方向投影到接受阴影的平面. ...

  9. Eclipse个最实用的快捷键

    一个Eclipse骨灰级开发人员总结了他觉得最实用但又不太为人所知的快捷键组合.通过这些组合能够更加easy的浏览源码,使得总体的开发效率和质量得到提升.     1. ctrl+shift+r:打开 ...

  10. 局域网简单的SVN服务器的搭建

            最近组织在做一个比较大的项目,需要多人参与配合,经常会对项目文件增删查改,因此使用了SVN作为项目管理工具.但大家都很"盲",所以搭建SVN服务器的任务就落在了我这 ...