目录
一、插件工程
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文件的文件名称的命名是什么
注
此文是个人学习的总结,转载请注明出处,谢谢!