Qt插件开发入门(两种方法:High-Level API接口,Low-Level API接口)
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接口)的更多相关文章
- jQuery插件开发的两种方法及$.fn.extend的详解(转)
jQuery插件开发的两种方法及$.fn.extend的详解 jQuery插件开发分为两种:1 类级别.2 对象级别,下面为大家详细介绍下 jQuery插件开发分为两种: 1 类级别 类级别你可以 ...
- jQuery插件开发的两种方法及$.fn.extend的详解
jQuery插件开发分为两种: 1 类级别 类级别你可以理解为拓展jquery类,最明显的例子是$.ajax(...),相当于静态方法. 开发扩展其方法时使用$.extend方法,即jQuery.ex ...
- Qt连接数据库的两种方法
我曾经想过,无论在哪个平台下开发,都不要再接触SQL Server了,但显然不行.我们是来看世界的,不是来改变世界的,想通就好. 前两天,尝试了一下Qt下远程访问数据库.在macOS下,用Qt 5.1 ...
- Qt中的布局浅析与弹簧的使用,以及Qt居中的两种方法
1. 布局 为什么要布局: 布局之后窗口的排列是有序的 布局之后窗口的大小发生变化, 控件的大小也会对应变化 如果不对控件布局, 窗口显示出来之后有些控件的看不到的 布局是可以嵌套使用 常用的布局方式 ...
- vue插件开发的两种方法:以通知插件toastr为例
方法一: 1.写插件: 在 src 文件夹下面建 lib 文件夹用于存放插件,lib 文件夹下再建toastr文件夹,在toastr文件夹下新建 toastr.js 和 toastr.vue两个文件. ...
- jQuery插件开发的两种方法
1 类级别 类级别你可以理解为拓展jquery类,最明显的例子是$.ajax(...),相当于静态方法. 开发扩展其方法时使用$.extend方法,即jQuery.extend(object); $. ...
- Qt 之 设置窗口边框的圆角(使用QSS和PaintEvent两种方法)
Qt在设置窗口边框圆角时有两种方式,一种是设置样式,另一种是在paintEvent事件中绘制窗口.下面分别叙述用这两种方式来实现窗口边框圆角的效果. 一.使用setStyleSheet方法 this- ...
- [转]Qt中定时器使用的两种方法
Qt中定时器的使用有两种方法,一种是使用QObject类提供的定时器,还有一种就是使用QTimer类. 其精确度一般依赖于操作系统和硬件,但一般支持20ms.下面将分别介绍两种方法来使用定时器. 方法 ...
- Qt中显示图像的两种方法
博客转载自:https://blog.csdn.net/lg1259156776/article/details/52325361 在Qt中处理图片一般都要用到QImage类,但是QImage的对象不 ...
- QT中获取选中的radioButton的两种方法(动态取得控件的objectName之后,对名字进行比较)
QT中获取选中的radioButton的两种方法 QT中要获取radioButton组中被选中的那个按钮,可以采用两种如下两种办法进行: 方法一:采用对象名称进行获取 代码: 1 QRadioBu ...
随机推荐
- 2017.8.9在虚拟机中安装linux系统
0 安装前提 已经安装好VM,并且配置好,具体过程参看随笔:2017.8.5 VMware的介绍与安装 相关随笔:2017.8.5 Linux达人养成计划 I 需要知道的概念:VMWare,虚拟机,真 ...
- Docker以及registry的入门学习安装
一.前言 如果你是数据中心或云计算IT圈子的人,我想你一定听过Docker,关于它们的新闻从未间断过.Docker的发展历程虽然算不上太长,但是自2014年6月Docker 1.0 正式发布,但是Do ...
- 用Fiddler或Charles进行mock数据搭建测试环境
转载:http://blog.csdn.net/qqYJ5/article/details/62216582 应用场景:服务器数据不符合测试条件或者服务器未开发完成时,我们可以通过在本地创建数据来达到 ...
- Windows为什么双击打开‘我的电脑’, 没有了‘前进’‘ 后退’‘向上’等按钮?
如图所示 点击查看 工具栏 标准按钮即可 左侧的数值虚线可以拖动到任意,还可以添加按钮如搜索,删除,复制,剪切等
- Codeforces #263 div2 解题报告
比赛链接:http://codeforces.com/contest/462 这次比赛的时候,刚刚注冊的时候非常想好好的做一下,可是网上喝了个小酒之后.也就迷迷糊糊地看了题目,做了几题.一觉醒来发现r ...
- 倒计时:CountDownLatch(火箭发射前的准备)读书笔记
这是一个非常实用的多线程控制工具类,经典的场景就是 火箭发射,在火箭发射前,为了保证万无一失,往往还要进行各项设备,仪器的检查,只有等待所有的检查完毕后,引擎才能点火, CountDown ...
- Shell 进度条效果的一个实现
#!/bin/bash processBar() { now=$ all=$ percent=`awk BEGIN'{printf "%f", ('$now'/'$all')}'` ...
- SQLite使用事务
关键代码 SQLiteDatabase db = myDataHelper.getWritableDatabase(); // 开启事务 db.beginTransaction(); try{ db. ...
- 偶遇 smon 进程cpu 开销高异常分析
今天突然发现线上一台oracle 数据库 servercpu 跑的非常高.感觉不是非常正常,细致看了下.发现是smon 进程吃掉了一个cpu. 那么这个smon 进程究竟在倒腾啥玩意 对smon 进程 ...
- MySql RESTRICT CASCADE SET NULL
主表,从表[MySql] //http://my.oschina.net/cart/blog/277624 空.RESTRICT.NO ACTION 删除:从表记录不存在时,主表才可以删除.删除从表, ...