当前位置: 首页 > article >正文

在Visual Studio 2022中实现Qt插件开发

在Visual Studio 2022中实现Qt插件开发

习惯在VS开发,不习惯在QCreator上开发,所以摸索出在vs2022里开发Qt插件的方法,还是挺方便的。核心的内容其实是一样的,只有几点要注意。


1.在VS上创建QT主程序

这一步在VS上正常创建Qt项目,或者说解决方案。如果你已经拥有现成的Qt项目,只想为它开发Qt插件的话,那也不用做任何配置和代码修改,这也是Qt插件机制的好处。

我现在创建了一个最简单的Qt主项目,我的需求是,我在主项目下点击PushButton按钮,按钮可以调用我待会要写的Qt插件,并输出这个插件的函数——》即在控制台上打印Hello Plugin。就这么简单的需求,能够代表这种插件机制的作用了。


2.在同一个解决方案下创建一个新的Qt项目,作为Qt插件

这里先说清楚Qt插件的基本组成,其实Qt插件本质上跟平时用的第三方库一样,或者说跟开发DLL动态链接库一样。我们需要提供些什么内容给主程序呢?

答案是两部分,部分1:h头文件(接口)和部分2:dll文件(隐藏的具体实现)

那其实主要逻辑跟DLL动态链接库的开发类似,我们目的也是使这个新的Qt项目能够生成一个DLL。然后为主程序提供我们定义的接口文件,和生成的DLL文件。

但是我们不创建DLL项目,我们直接创建一个Qt项目,这样可以免去我们花时间去为项目配置Qt。

创建一个Qt Widget Application项目。自动生成的初始模板文件其实我们都可以删掉,因为不影响,我们目标明确,就是要写好刚刚说的两个部分。

既然我要最后编译要生成DLL文件,那么我们应该去项目配置里,将配置类型从(.exe)改为(.dll)
在这里插入图片描述

2.1 部分1:h头文件(接口文件)

我们要写一个接口文件,代表我们要提供给主程序调用的功能。

要求是一个纯虚基类,

那我们就写一个纯虚基类的头文件作为接口:Interface.h

#pragma once
#include <QWidget>
#include <QtCore/qplugin.h>

#define InterfaceIID "com.QGL.Interface"	//****关注点1*****

class Interface
{
public:
	virtual ~Interface() = default;
	virtual void print() = 0;	//我要提供给主程序调用的函数
};

Q_DECLARE_INTERFACE(Interface, InterfaceIID); //****关注点2*****

与平时的基类写法相比,多了两个地方,就是我在注释里留下的关注点1关注点2

  • 关注点1:#define InterfaceIID "com.QGL.Interface"
    我将"com.QGL.Interface"这个字符串定义为InterfaceIID。这个字符串作为这个接口的唯一标识符。如果你记得住这个字符串,且愿意修改这个字符串后,不厌其烦地在每个用到它的地方都修改一遍的话,那可以不写这个define。

  • 关注点2:Q_DECLARE_INTERFACE(Interface, InterfaceIID);
    如果说关注点1是为方便,那关注点2这一行则是必须。这里可以理解为将你写的这个接口类,与接口标识符绑定起来,并告诉Qt,这个接口类是个Qt插件。

那我们最基本的一个Hello World级别的接口文件就写好了。

2.2 部分2:dll内容部分

dll里面放的是具体的实现内容,并隐藏起来,因为主程序只需要调用接口,不需要知道具体怎么实现的。

那现在我们要为接口类写好具体的实现,首先就是创建一个继承类(派生类),我命名这个继承类为InterfaceImp。

我创建一个继承自Qt Widget的类,作为InterfaceImp,那还得实现我的刚刚写好的接口:

class InterfaceImp : public QWidget, public Interface

这都没问题,后面自然就是跟着Q_OBJECT这个宏,还有就是具体的函数实现。
InterfaceImp.h:

#pragma once
#include <QWidget>
#include "Interface.h"
#include "ui_InterfaceImp .h"

//下面三行都是自动创建的东西
QT_BEGIN_NAMESPACE
namespace Ui { class InterfaceImpClass; };
QT_END_NAMESPACE

//下面的内容才是要关注的点
class InterfaceImp : public QWidget, public Interface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID InterfaceIID)		//****关注点1*****
    Q_INTERFACES(Interface)					//****关注点2*****

public:
    InterfaceImp (QWidget *parent = nullptr);
    ~InterfaceImp ();
	//接口里面print函数的具体实现
    void print();

private:
    Ui::InterfaceImpClass *ui;
};

相比普通一个继承QWidget的类,这个代码的注释里又出现了两个的关注点,这两个是构建Qt插件的重要步骤之一。

  • 关注点1:Q_PLUGIN_METADATA宏将插件的信息嵌入到生成的dll中,类似dll开发时的导出声明。
    Q_PLUGIN_METADATA(IID "插件接口标识符" FILE "元数据文件.json")
    这个宏时这样的,FILE我们暂时不关注。我们要知道的是,得有这一行,这个派生类才会导出生成dll文件。

  • 关注点2:Q_INTERFACES宏用于声明一个类实现了哪些接口,填上刚刚写好的接口类的类名。我要明确指出,这个类是实现了Interface这个接口,这样当插件被加载时,才能知道这个类时Interface接口类的具体实现。

至于cpp文件,其实只需要实现print函数的就可以了,我就简单列出来吧
InterfaceImp.cpp:

#include "InterfaceImp.h"
#include <QDebug>

InterfaceImp::InterfaceImp(QWidget *parent) : QWidget(parent)
    , ui(new Ui::InterfaceImpClass())
{
    ui->setupUi(this);
}

InterfaceImp::~InterfaceImp()
{
    delete ui;
}

//以上代码自动生成,以下时print函数的具体实现
void InterfaceImp::print()
{
    qDebug() << "Hello Plugin";
}


3.编译生成该插件项目

其实看完第2步骤,我们整个Qt插件项目只需要三个代码文件,其他代码文件无关痛痒,删掉也没事。对项目编译成功生成后,在生成目录里找,能够找到一个生成的该插件的.dll文件(假设名字就是Interface.dll)。

至此,我们两部分都有了,部分1接口文件有了:interface.h,部分2接口文件也有了:interface.dll。那么我们的插件就完成了。

接下来考虑怎么用了。

关于这两部分怎么存放,其实存放哪里都不是问题,关键是能够然主程序识别到就行。

我们该插件其实就是第三方库,我们可以像第三方库那样规范化存放,创建一个Plugin文件夹,然后里面创建bin文件夹和include文件夹。(因为不是静态链接库,所以不需要Lib文件夹)。我们把bin文件夹路径加入到系统环境变量path下,include文件夹路径加入到主项目的配置中,加到附加包含目录那一项里面(跟平时用第三方库是一样的配置方法)

总之,想方法让主程序能够找到它们。


4.在主程序里调用Qt插件

这里再次重复我的需求:我在主项目下界面点击PushButton按钮,按钮可以调用我待会要写的Qt插件,并输出这个插件的函数——》即在控制台上打印Hello Plugin。

关于QPushButton的添加,槽函数的声明这些,不再赘述。看着我的需求,我就是要在按钮与clicked信号对应的槽函数下去调用刚刚写好的插件。那这一步骤的关键就是,怎么调用?

Qt本身就提供一个插件加载器,QPluginLoader,我们将用它来加载。

我就放按钮的槽函数:

void MainWindow::buttonSlot()
{
	//创建QPluginLoader 
	QPluginLoader loader;
	//创建QDir,用于文件路径操作,我们通过QDir来获取Interface.dll的准确路径
	//因为QPluginLoader是根据路径来加载dll文件,所以利用QDir来方便获得它的路径
	//此处qApp->applicationDirPath()代表dll文件所在地
    QDir dir(qApp->applicationDirPath());
    loader.setFileName(dir.filePath("Interface.dll"));
    if (!loader.load())
    {
        QMessageBox::critical(this, "", loader.errorString());
        return;
    }
	
	//成功加载后,将加载的instance类型转换为Interface,这里意味着要包含Interface.h接口的头文件。
    Interface* interface = qobject_cast<Interface*>(loader.instance());
    if (interface)
    {
    	//然后正常调用接口提供的方法。
        interface->print();
    }
}

5. Qt插件与dll动态链接库的区别

看上面加载插件和调用函数的代码你应该能察觉得到不同之处。Qt插件多了个运行时加载的这个步骤,在点击按钮之后才去加载这个插件,即使找不到dll文件,也不会影响主程序的正常运行。而dll动态链接库是拿来直接用,如果dll文件找不到了,主程序就会报错了。


http://www.kler.cn/a/581081.html

相关文章:

  • 蓝桥杯嵌入式组第七届省赛题目解析+STM32G431RBT6实现源码
  • Python从入门到精通1:FastAPI
  • 现代优雅品牌杂志广告邀请函设计衬线手写英文字体 Buttercy Font Duo
  • 深入解析 C 语言中含数组和指针的构造体与共同体内存计算
  • 【数据结构】二叉搜索树、平衡搜索树、红黑树
  • HTML <head> 元素详解:网页头部的关键组成部分
  • ragflow-组件可视化工具 es默认用户名elastic
  • sonarqube+SonarScanner+postpresql+jenkins
  • 解决 React 中的 Hydration Failed 错误
  • 打造智能钉钉机器人:借助智谱GLM-4-Flash实现高效智能回复(文末附源码)
  • leetcode1143.最长公共子序列
  • 【设计模式】掌握建造者模式:如何优雅地解决复杂对象创建难题?
  • 用Qt手搓AI助手,挑战24小时开发DeepSeek Assistant!
  • 分布式系统中分布式ID生成方案的技术详解
  • 如何在c# 项目中使用redis
  • 跟踪napi_gro_receive_entry时IP头信息缺失的分析
  • Spring有哪些缺点?
  • k8s面试题总结(十四)
  • Linux开发工具----vim
  • Pandas数据清洗实战之清洗猫眼电影