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
版权声明:本文为博主原创文章,转载请附上博文链接!

posted @ 2019-01-09 21:30  findumars  Views(6278)  Comments(0Edit  收藏  举报