目录
本文是实现应用程序插件功能的一个示例,示例包含两个Qt项目,一个是应用程序项目,一个是插件设计项目。本例中实现的是基于QWidget的ui图形插件。
带有插件扩展功能的应用程序
创建一个界面程序项目,在界面中拉入一个QWidget,我们的小部件插件会显示在这个QWidget中。
API接口——设计插件类的基类
在项目中创建一个C++类,它将是所有用户编写的插件类的基类,因为在没有实现派生类时创建这个类的内存对象没有任何意义,故这个基类应当被设置为抽象基类。
我们通过编写虚函数来规范插件的功能,虚函数就是API接口函数。对于虚函数,我们不能提前确定函数的具体实现,但是我们可以提前确定函数的形参和返回类型,通过两个已知的特征,我们就可以提前在应用程序中使用这些函数,这是应用程序能够实现插件功能的基本原理。
插件基类除了需要被设为抽象基类外,还需要在指定位置加入一条简单的Qt宏代码。
以下是这个插件接口类的代码:
#include "QWidget"
class ToolInterface
{
public:
virtual ~ToolInterface(){}
virtual void draw() = 0;
virtual void setParent(QWidget * parent = 0) = 0;
virtual QString name() = 0;
virtual void resize(int width, int height) = 0;
virtual void show() = 0;
};
//注意:
//1.插件类必须包含以下宏代码, 该宏用作将IID标识符与指定的类名相关联.
//2.IID自行设定, 是一个字符串, 用作标识插件对象的唯一性. 比如创建了多个不同接口的插件,
//若其IID相同, 那么加载插件时, 创建的插件对象指针就会被指向同一处内存, 显然这不是预期的.
//3.用户在编写插件类的派生类时, 需要用另一个宏指出基类的IID.
Q_DECLARE_INTERFACE(ToolInterface, "0")//'ToolInterface'是className '0'是IID.
可以看到设定了一些setParent()、resize()、show()之类的函数,没错,小部件插件本质就是一个小部件类,只是我们在创建应用程序的时候不知道它具体是个什么小部件而已,它有可能是个按钮,或者是个菜单,亦或是一个Label,我们在后面的一个用户插件示例中实现的就是一个Label。
加载插件
需要事先在项目文件夹中创建一个用于存放用户编写的插件的文件夹,我们这里创建了一个名为pubgins的文件夹:
然后就需要编写一个加载它里面的插件文件的函数了,在编写前,需要先创建一个插件类的指针数组对象,对象被创建在项目类中:
然后就是加载插件,这个加载插件的函数名为loadPlugin(),它被创建在项目类中,其实现为:
#include "QDir"
#include "QPluginLoader"
#include "QAction"
void MainWindow::loadPlugin()
{
QDir pluginsDir("C:\\Users\\Administrator\\Documents\\untitled10\\plugins");//存在插件的文件夹
foreach(QString fileName, pluginsDir.entryList(QDir::Files)) //遍历文件夹中的所有文件
{
cout << "MainWindow::loadPlugin - " << endl;
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName)); //用Qt插件加载类一一读取文件
QObject * plugin = pluginLoader.instance(); //以QObject *返回创建的插件指针
if(plugin)
{
ToolInterface * ti = qobject_cast<ToolInterface *>(plugin); //强制转换成我们的插件类型
if(!ti)
continue;
toolInterfaces.append(ti); //保存插件指针到插件数组中
cout << "MainWindow::loadPlugin - " << qPrintable(ti->name()) << endl;
ui->mainToolBar->addAction(ti->name()); //以插件name为名工具栏添加一个按钮
}
}
//这个信号槽绑定使得工具栏按钮的点击事件全部被链接到槽_tool_action中
connect(ui->mainToolBar, &QToolBar::actionTriggered,
this, &MainWindow::_tool_action);
}
void MainWindow::_tool_action(QAction * action)
{
foreach (ToolInterface * ti, toolInterfaces) //遍历插件数组中的所有插件
{
if(ti->name() == action->text()) //若与传入槽中的按钮所代表的插件名相同
{
//activeTI = ti;
cout << qPrintable(action->text()) << endl;
ti->setParent(ui->widget); //设置小部件插件的parent为项目的ui->widget小部件
ti->draw(); //绘制插件
ti->show(); //显示插件
break; //结束循环
}
}
}
可见还设置了一个信号槽用于通过点击按钮来显示指定的小部件插件。
在插件文件夹没有任何文件的情况下运行项目显示如下:
这个应用程序显然是很枯燥的,但是在插件文件夹中放入插件文件后就会有所不一样了,这也正是插件功能的魅力所在。
接下来我们就以应用程序用户的身份来编写插件:
编写插件
用户编写插件需要应用程序官方提供的相关SDK编程包,这里就是插件基类的头文件及其cpp文件。一般情况是要将插件的.h和.cpp文件制作成动态链接库文件,为方便起见这里就直接用源码文件了。
编写插件基类的派生类
创建一个Qt Widgets项目,更改pro文件为:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TEMPLATE = app
INCLUDEPATH += ../untitled10 #添加一个包含插件基类源码文件的目录
SOURCES += \
main.cpp \
toolplugin.cpp \
../untitled10/toolinterface.cpp
HEADERS += \
toolplugin.h \
../untitled10/toolinterface.h
“../untitled10/toolinterface.cpp”和“../untitled10/toolinterface.h”即是插件基类源码文件的地址。此时这个项目是个app项目,我们仅作为调试用。
接下来就是派生自插件基类的插件类:
#include "toolinterface.h"
class ToolPlugin : public QWidget, ToolInterface
{
Q_OBJECT
//插件基类派生类必须要包含以下Qt宏代码, 它们将在应用程序加载这个插件时起作用.
Q_PLUGIN_METADATA(IID "0") //IID必须与插件类设定的IID相同
Q_INTERFACES(ToolInterface)//参数就是基类名称
public:
ToolPlugin(QWidget * parent = 0);
~ToolPlugin();
void setParent(QWidget * parent = 0);
void draw();
QString name();
virtual void resize(int width, int height);
virtual void show();
};
可以就看到这个插件类有两个基类,因为本文要编写小部件插件,故另一个基类是QWidget,若是其它插件,则必须是QObject,即便你不使用这个基类。注意还要在类定义中加入两条Qt宏。
类实现为:
ToolPlugin::ToolPlugin(QWidget * parent)
: QWidget(parent)
{
resize(200, 50);
}
ToolPlugin::~ToolPlugin()
{
}
void ToolPlugin::setParent(QWidget * parent)
{
QWidget::setParent(parent);
}
#include "QLabel"
void ToolPlugin::draw()
{
QLabel * label = new QLabel(this);
QPalette pa;
pa.setColor(QPalette::WindowText, QColor(250, 128, 10));
label->setPalette(pa);
label->setFont(QFont("微软雅黑", 18, 75, true));
label->setText("Hello World!");
}
QString ToolPlugin::name()
{
return "Orange";
}
void ToolPlugin::show()
{
QWidget::show();
}
void ToolPlugin::resize(int width, int height)
{
QWidget::resize(width, height);
}
main.cpp:
#include <QApplication>
#include "toolplugin.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ToolPlugin w;
w.draw();
w.show();
return a.exec();
}
运行后截图:
生成插件
无需另创项目,只需要更改pro文件,即可将这个app项目转变成插件项目,pro文件更改如下:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
#TEMPLATE = app
#INCLUDEPATH += ../untitled10 #添加一个包含插件基类源码文件的目录
#SOURCES += \
# main.cpp \
# toolplugin.cpp \
# ../untitled10/toolinterface.cpp
#HEADERS += \
# toolplugin.h \
# ../untitled10/toolinterface.h
TEMPLATE = lib #设定项目要创建库文件
CONFIG += plugin #通知qmake要创建插件
HEADERS = toolplugin.h
SOURCES = toolplugin.cpp
INCLUDEPATH += ../untitled10 #添加一个包含目录
DESTDIR = ../untitled10/plugins#指定生成dll文件的存放目录
TARGET = Qrange #dll文件名字
右键单击项目,点击“构建”,之后查看plugins文件夹,截图如下:
插件已经生成,接下来需要加载插件了。
在应用程序中加载插件
前面我们很方便的将插件直接生成在了应用程序项目的插件文件夹中,现在直接运行应用程序即可:
可以看到应用程序多了一个名为“Orange”的按钮,它正是我们创建的插件的名称,点击它:
小部件插件完美显示。
特别注意
-
编写插件时的插件基类与应用程序中的插件基类必须完全相同,类名和类函数参数、返回类型都要完全一致,否则插件被加载时基类将不会被识别。
-
debug模式下生成的插件只能用于debug模式下的应用程序,若用于release模式的应用程序,请生成release模式的插件,否则插件将完全不能被识别。