Qt C++ 插件编程(动态加载DLL)

目录

一、插件工程

1、插件类

VS 2017创建一个Qt的Plugin工程

①纯虚函数接口

②虚函数实现类

③全局函数

二、动态加载DLL工程

VS 2017创建一个加载DLL的工程

1、调用插件类

2、调用全局函数

3、类内函数

方法一:

方法二:

总结

插件类工程

1、纯虚函数

2、接口类的实现

3、全局函数

4、VS 编译器中

5、VS配置uic编译Qt的.ui文件



一、插件工程

1、插件类

VS 2017创建一个Qt的Plugin工程

直接默认下一步就好了

然后可以得到,图中的三个文件

创建完成可以直接编译一下,是可以自动生成.lib、.dll、.exp这三个文件的。如果不行后续操作没有办法完成。

①纯虚函数接口

接着添加一个,一个接口文件 ICalculate.h。直接新建一个头文件即可。

#pragma once

//该常量用作插件接口的唯一标识符
#define QTPLUGIN_ICALCULATE_IID "spi.plugin.icalculate.plugin01"

class ICalculate
{
	Q_INTERFACES(ICalculate)
public:
	virtual int addInt(int a, int b) = 0;

	virtual void showDialog() = 0;
};

//用于声明一个类为 Qt 插件接口
Q_DECLARE_INTERFACE(ICalculate, QTPLUGIN_ICALCULATE_IID)

②虚函数实现类

plugin01_glabal.h文件的内容不需要改变,直接接默认不动即可。

在plugin01.h文件中编辑下面代码

#pragma once

#include "plugin01_global.h"
#include "InputNewProductParameterUI.h"
#include "ICalculate.h"

#include <QObject>
#include <QSharedPointer>


class PLUGIN01_EXPORT plugin01 : public QObject, public ICalculate
{
	Q_OBJECT
	//ICalculate是一个纯虚函数的接口,Qt插件系统仍然需要了解你的插件实现了哪些接口
	Q_INTERFACES(ICalculate)
	//Q_PLUGIN_METADATA 宏声明了插件的元数据信息,其中 IID 参数指定了插件的唯一标识符。
	Q_PLUGIN_METADATA(IID QTPLUGIN_ICALCULATE_IID)

public:
	plugin01();
	~plugin01();

	static plugin01* getInstance()
	{
		if (m_this == nullptr)
		{
			m_this = new plugin01();
		}
		return m_this;
	}

	virtual int addInt(int a, int b) override;

	virtual void showDialog() override;

private:
	QSharedPointer<InputNewProductParameterUI> m_inputUI;
	static plugin01* m_this;
};

//使用extern 标识符注意在cpp中做定义,.h中做声明,否则会提示函数重定义
//extern "C" void testFunc(QString & str);
extern "C" __declspec(dllexport) void testFunc(QString & str);

在plugin01.cpp文件中写入下面代码

#include "plugin01.h"

plugin01* plugin01::m_this = nullptr;

plugin01::plugin01()
{
	m_this = this;

	m_inputUI = QSharedPointer<InputNewProductParameterUI>::create();
}

plugin01::~plugin01()
{
	// 在析构函数中清理 m_this
	if (m_this == this)
	{
		m_this = nullptr;
	}
}

int plugin01::addInt(int a, int b)
{
	int tmp = a + b;
	return tmp;
}

void plugin01::showDialog()
{
	m_inputUI->exec();
}

extern "C" __declspec(dllexport) void testFunc(QString & str)
{
	str = "this is plugin return string";
}

③全局函数

类外单独使用extern "C" 声明函数,__declspec(dllexport) 符号导出该全局函数到dll中。

编译通过时候可以使用VS的Native Tools Command Prompt for 2017工具解析dll中导出的函数有哪些。

命令:

dumpbin /EXPORTS plugin01.dll

下图中就可以看到编译过的dll可以看到导出的函数:(红色是类中的函数,绿色的是全局函数)

二、动态加载DLL工程

VS 2017创建一个加载DLL的工程

这两个都可以,如果自己封装的插件类包含QWidget类的话就需要包含GUI、QWidget模块,然后使用QApplication创建一个带图形界面的应用程序,QCoreApplication是不带图形界面的应用程序。

1、调用插件类

main里面直接写入下面代码

#include "plugin01.h"
#include <QLibrary>
#include <QString>

// 获取实例(函数指针)
typedef plugin01* (*CreatePluginFunction)();
typedef void (*FUNC_GUAN_SHOW)(QString &str);

//#define guan

int main(int argc, char *argv[])
{
	//QCoreApplication a(argc, argv);
	QApplication app(argc, argv);
// 动态加载 DLL
	QLibrary pluginLib("plugin01.dll");//可以不加载.dll这个后缀名,官方解释会自动根据开发平台检索后缀。


	if (pluginLib.load()) {

		FUNC_GUAN_SHOW coutStr = (FUNC_GUAN_SHOW)pluginLib.resolve("testFunc");
		if (coutStr)
		{
			QString qDstr = "";
			coutStr(qDstr);
			qDebug() << __FUNCDNAME__ << qDstr;
		}

		//两个方法都可以
		//CreatePluginFunction createPlugin = reinterpret_cast<CreatePluginFunction>(pluginLib.resolve("qt_plugin_instance"));
		CreatePluginFunction createPlugin = (CreatePluginFunction)pluginLib.resolve("qt_plugin_instance");

		if (createPlugin) {
			plugin01 *process = createPlugin();

			if (process) {
				process->showDialog();
				process->addInt(10, 35);
				qDebug() << __FUNCDNAME__ << process->addInt(10, 35);
			}
			else {
				qDebug() << "Failed to create plugin instance.";
			}
		}
		else {
			qDebug() << "Failed to resolve createPlugin function.";
		}

		// 卸载 DLL
		pluginLib.unload();
	}
	else {
		qDebug() << "Failed to load DLL:" << pluginLib.errorString().data();
	}


	return app.exec();
}

2、调用全局函数

main的上方中我定义了函数指针,名字随便取的。

typedef void (*FUNC_GUAN_SHOW)(QString &str);

然后根据下文的代码,就可以拿到用到全局函数的地址

FUNC_GUAN_SHOW coutStr = (FUNC_GUAN_SHOW)pluginLib.resolve("testFunc");
if (coutStr)
{
	QString qDstr = "";
	coutStr(qDstr);
	qDebug() << __FUNCDNAME__ << qDstr;
}

3、类内函数

方法一:

类内的函数没有像全局变量那样直接被导出,然后需要用到qt的一个全局的宏获取这个类对象的地址,由 Qt 的元对象系统生成的函数,它返回插件实例的指针。在解析dll的时候是可以看到有这个插件实例的。

CreatePluginFunction createPlugin = reinterpret_cast<CreatePluginFunction>(pluginLib.resolve("qt_plugin_instance"));

然后创建一个函数指针指向这个对象,获取到这个实例对象的地址。

if (createPlugin) {
	plugin01 *process = createPlugin();

	if (process) {
		process->showDialog();
		process->addInt(10, 35);
		qDebug() << __FUNCDNAME__ << process->addInt(10, 35);
	}
	else {
		qDebug() << "Failed to create plugin instance.";
	}
}
方法二:

增加一个全局函数,然后在全局函数中去创建对象。

plugin01.h

extern "C" __declspec(dllexport) plugin01* getIns();

plugin01.cpp

extern "C" __declspec(dllexport) plugin01 * getIns()
{
	return new plugin01();
}

在调用dll的工程main函数中

FUNC_GETINSTANCE createPlugin = (FUNC_GETINSTANCE)pluginLib.resolve("getIns");
createPlugin()->showDialog();
int tmp = createPlugin()->addInt(10, 25);

注:这个函数在上文中没有,因为感觉不如直接使用qt自动生成的元对象去调更好一些。

总结

插件类工程

1、纯虚函数

定义接口类的时候需要用到插件接口的唯一标识符:QTPLUGIN_ICALCULATE_IID "定义一个唯一的字符串"

声明这个类是接口类:Q_DECLARE_INTERFACE(纯虚函数类类名,唯一标识符)

声明这个类在写的时候是作为接口:Q_INTERFACES(纯虚函数类类名)

2、接口类的实现

声明实现接口的父类是:Q_INTERFACES(纯虚函数类类名)

Qt 插件接口的类中声明元数据信息是:Q_PLUGIN_METADATA(IID 唯一标识符)

3、全局函数

需使用extern "C" 时,标识符注意在cpp中做定义,.h中做声明,否则会提示函数重定义。

需使用 __declspec(dllexport),用于导出全局函数到dll中。

4、VS 编译器中

需预加载包含插件类中的头文件,直接整个目录包含在开发的时候会方便很多,不用实时的更新目录的内容。

5、VS配置uic编译Qt的.ui文件

Directory 是输出的目录路径

File Name是输出此.ui文件的文件名称的命名是什么

此文是个人学习的总结,转载请注明出处,谢谢!

  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值