【开源】基于Qt的跨平台插件式开发框架QCPFrame(二)
六、组件
对于QCPFrame而言,一切皆插件的思想决定了其主要业务和功能都将以组件的形式存在,因此本章内容我们来讲讲QCPFrame组件的开发。
1. 创建一个插件工程
如果我告诉你拷贝一个工程,然后修改*.pro和PluginIO类。你一定会觉得很Low,可是这也确实是最为快捷的一种创建插件工程的方式。好了,下面我们来看怎么老老实实创建一个QCPFrame插件工程。
1.1 新建动态链接库工程
你可以通过Qt创建一个动态库工程,如下图:
1.2. 添加接口文件
在工程中添加interface文件夹下面的两个接口文件“coreinterface.h”和“plugininterface.h”。
1.3. 添加组件基类库文件
在工程右键菜单选择“添加库...”,找到QCPF_PluginModel.h及QCPF_PluginModel.lib,典型的路径如下:
unix|win32: LIBS += -L$$PWD/../../../bin/ -lQCPF_PluginModel
INCLUDEPATH += $$PWD/../../QCPF_PluginModel
DEPENDPATH += $$PWD/../../QCPF_PluginModel
1.4. 新建组件实现类
在工程右键菜单选择“Add New”,创建“PluginIO.h/PluginIO.cpp”。其头文件如下所示:
PluginIO.h
#ifndef QCPF_PLUGINIO_H
#define QCPF_PLUGINIO_H
#include "../../QCPF_PluginModel/qcpf_nonsystempluginmodel.h"
class PluginIO : public QCPF_NonSystemPluginModel
{
Q_OBJECT
Q_PLUGIN_METADATA(IID Plugin_Interface_iid FILE "QPlugin.json")
Q_INTERFACES(Plugin_Interface)
public:
PluginIO();
~PluginIO();
public:
static PluginIO* getInstance();//静态获取实例
};
#endif
PluginIO.cpp
#include "PluginIO.h"
#include <QVariant>
#include <QStringLiteral>
#include <QAction>
PluginIO* instance;
PluginIO::PluginIO()
{
instance = this;
I_PluginID = QStringLiteral("QPlugin1");
I_PluginAliasName = QStringLiteral("QPlugin1");
I_PluginAuther = QStringLiteral("xxxx");
I_PluginVersion = QStringLiteral("1.0.0.1");
I_PluginComment = QStringLiteral("QPlugin1 comment");
I_PluginAuthority = AT_USER1;
I_PluginTag = QStringLiteral("NON_SYSTEM\\DataSave");
}
PluginIO::~PluginIO(){}
PluginIO* PluginIO::getInstance()
{
return instance;
}
PluginIO.h中的代码#include "../../QCPF_PluginModel/qcpf_nonsystempluginmodel.h"表示包含非系统组件类的头文件,如果你要创建的是一个系统组件,那么就包含qcpf_systempluginmodel.h。同样的,继承的类也要与之对应。
1.4. 创建QPlugin.json文件
在项目目录下创建QPlugin.json空文件,用于临时存储meta数据。
至此一个典型的插件工程就建好了。你可以根据你的业务需求在“PluginIO.h/PluginIO.cpp”中添加代码,或者在工程中添加文件。但请记住“PluginIO.h/PluginIO.cpp”始终是你与系统之间通讯生命周期中的接口文件。
2. PluginIO文件功能及代码风格建议
PluginIO文件是组件与内核连接的主要渠道,我们以系统组件“QCPF_UserManager”为例来讲讲如何在该文件中添加功能代码。QCPF_UserManager.h代码如下:
/*
Author : Jamie.Tong
QQ : 260271262
Data : 2020-09-07
License: GPL v3.0
*/
#ifndef QCPF_PLUGINIO_H
#define QCPF_PLUGINIO_H
#include "../../QCPF_PluginModel/qcpf_systempluginmodel.h"
class PluginIO : public QCPF_SystemPluginModel
{
Q_OBJECT
Q_PLUGIN_METADATA(IID Plugin_Interface_iid FILE "QPlugin.json")
Q_INTERFACES(Plugin_Interface)
public:
PluginIO();
~PluginIO();
void InitActionList(Plugin_Interface* plugin) Q_DECL_OVERRIDE;
void InitFunctionList(Plugin_Interface* plugin) Q_DECL_OVERRIDE;
void InitWidgetList(Plugin_Interface* plugin) Q_DECL_OVERRIDE;
public:
tagOutputInfo tinfo;
static PluginIO* getInstance();
int GetUsersInfoFromJson();
private:
void Action_ShowUserManager(bool checkState);
int Function_VerifyLoginInfo(QVariant arg_in, QVariant& arg_out);
public slots:
//当core初始化时要执行的过程
int OnCoreInitialize() Q_DECL_OVERRIDE;
int slot_InputInfo(tagOutputInfo& info) Q_DECL_OVERRIDE;
};
#endif
其中 InitActionList用于初始化和收集本组件中的所有Action,InitFunctionList用于初始化和收集本组件中的所有Function,InitWidgetList用于初始化和收集本组件中的所有Widget。
getInstance() 用于获取this实例,在有些时候非常有用。
OnCoreInitialize()是组件被内核连接时会被调用的过程,在本组件中,我们在OnCoreInitialize过程中调用GetUserInfoFromJson()函数,目的是组件被连接时就获取到用户登录信息。
slot_InputInfo槽是用于接收输入信息而声名的,用于对输入的消息做出不同的响应。
建议用于Action的函数以“Action_”开头,用于Function的函数以“Function_”开头,这能便于我们快速归类和查找我们要编辑的功能模块。
/*
Author : Jamie.Tong
QQ : 260271262
Data : 2020-09-07
License: GPL v3.0
*/
#include "PluginIO.h"
#include <QWidget>
#include <QVariant>
#include <qobject.h>
#include <QStringLiteral>
#include <QAction>
#include "usermanager.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QJsonValue>
#include <QCryptographicHash>//用于md5加密
PluginIO* instance;
PluginIO::PluginIO()
{
instance = this;
I_PluginID = tr("QCPF_UserManager");
I_PluginAliasName = tr("User Manager");
I_PluginAuther = tr("Jamie.T");
I_PluginVersion = tr("1.0.0.4");
I_PluginComment = tr("Manage users.");
I_PluginTag = tr("SINGLETON\\SYSTEM\\USER_MANAGER");
I_PluginAuthority = AT_MANAGER1;
//GetUsersInfoFromJson();不能放在这里,基类的_core还没构造完
}
PluginIO::~PluginIO(){}
PluginIO* PluginIO::getInstance()
{
return instance;
}
//接口方法实现
/***************************************************
* 方法接口 *
***************************************************/
int PluginIO::OnCoreInitialize()
{
return GetUsersInfoFromJson();
}
void PluginIO::InitActionList(Plugin_Interface* plugin)
{
PluginActionInfo* pai = new PluginActionInfo();
pai->_actionName = tr("Show User Manager");
pai->_actionDetail = tr("User Manager.");
pai->_pAction = (FPTR_ACTION)(&PluginIO::Action_ShowUserManager);
plugin->I_ActionList.append(pai);
}
void PluginIO::InitFunctionList(Plugin_Interface* plugin)
{
//--------------------------------------------
PluginFunctionInfo* pfi_verifyLoginInfo = new PluginFunctionInfo();
pfi_verifyLoginInfo->_functionName = tr("VerifyLoginInfo");
pfi_verifyLoginInfo->_functionDetail = tr("Verify login info.");
pfi_verifyLoginInfo->_pFunction = (FPTR_FUNC_CLS)(&PluginIO::Function_VerifyLoginInfo);
plugin->I_FunctionList.append(pfi_verifyLoginInfo);
}
void PluginIO::InitWidgetList(Plugin_Interface* plugin)
{
QCPF_Model* core = (QCPF_Model*)_core;
PluginWidgetInfo *nFormInfo = new PluginWidgetInfo();
nFormInfo->_showType = ST_POPUP;
nFormInfo->_widget = new UserManager(core);
nFormInfo->_origWidth = nFormInfo->_widget->width();
nFormInfo->_origHeight = nFormInfo->_widget->height();
nFormInfo->_widgetDetail = tr("It's used for managing the Non-System plugins");
plugin->I_WidgetList.append(nFormInfo);
}
int PluginIO::slot_InputInfo(tagOutputInfo& info)
{
if(info._type == INFT_PLUGIN_COLLECT_FINISHED)
return 0;
}
int PluginIO::GetUsersInfoFromJson()
{
QFile file(_core->I_ApplicationDirPath + "/Data/User/Users.dat");
if(!file.open(QIODevice::ReadOnly))
return -1;
QByteArray allData = QByteArray::fromBase64(file.readAll());
file.close();
//进行JSON相关的处理
QJsonParseError json_error;
QJsonDocument jsonDoc(QJsonDocument::fromJson(allData, &json_error));
if(json_error.error != QJsonParseError::NoError)
return -2;
QJsonObject rootObj = jsonDoc.object();
//数据存入QJsonObject格式的rootObj中
if (rootObj.contains("Users"))
{
QJsonValue value = rootObj.value("Users");
if (value.isArray())
{
QJsonArray array = value.toArray();
int nSize = array.size();
_core->I_UserInfoLst.clear();
for (int i = 0; i < nSize; ++i)
{
UserInfo* tUser = new UserInfo();
tUser->_userName = array.at(i).toObject().value("UserName").toString();
tUser->_password = array.at(i).toObject().value("Password").toString();
QString authStr = array.at(i).toObject().value("Authority").toString();
int auth = authStr.toInt();
tUser->_authority = (AuthorityType)auth;
tUser->_createDatetime = array.at(i).toObject().value("Datetime").toString();
_core->I_UserInfoLst.append(tUser);
}
}
}
return 0;
}
/***********************************************************************
* action 函数指针所对应的回调函数
* *********************************************************************/
void PluginIO::Action_ShowUserManager(bool checkState)
{
QAction* actSender = (QAction*)sender();
if(I_WidgetList.count()>0)
{
foreach (PluginWidgetInfo* pwi, instance->I_WidgetList) {
if(pwi->_widget->objectName() == QStringLiteral("UserManager"))
{
pwi->_widget->setWindowIcon(actSender->icon());
((QDialog*)pwi->_widget)->exec();
}
}
}
}
struct tagUserInfo{
QString userName;
QString password;
};
Q_DECLARE_METATYPE(tagUserInfo)
int PluginIO::Function_VerifyLoginInfo(QVariant arg_in, QVariant& arg_out)
{
arg_out = false;
if(arg_in.canConvert<tagUserInfo>())
{
tagUserInfo userInfo =arg_in.value<tagUserInfo>();
foreach (UserInfo* uInfo, _core->I_UserInfoLst) {
if(uInfo->_userName == userInfo.userName)
{
if(uInfo->_password == userInfo.password)
{
_core->I_CurrentUserInfo._userName = uInfo->_userName;
_core->I_CurrentUserInfo._password = uInfo->_password;
_core->I_CurrentUserInfo._authority = uInfo->_authority;
_core->I_CurrentUserInfo._createDatetime = uInfo->_createDatetime;
_core->I_CurrentUserInfo._detail = uInfo->_detail;
arg_out = true;
}
break;
}
}
}
return 0;
}
int GetUsersInfoFromJson(),而代码开始部分的以“I_”开头的系列接口的填充将用于本组件信息的暴露。
小结:
QCPFrame插件开发的流程其实很简单,但是要实现复杂的功能还是有很多事情要做。仔细研究源码,你会发现QCPFrame的主要的功能如系统管理器,插件管理器,用户管理器等都是由插件来实现的,你完全可以从这些插件源码中看到一些插件设计及通讯的思路。为其嫁接新的业务时,需要考虑单独设计一个业务Model,并在一个插件的PluginIO中将该Model引入插件系统中。不要把业务与QCPFrame_Model进行耦合,那样就失去了框架最初设计的意义,我们只需要将其他业务的Model引入一个插件的PluginIO类中,使QCPF_Model/QCPF_ViewModel与你的业务Model在这一个插件中进行相互访问,即可解耦。即新的业务只跟该插件相关,而与QCPFrame插件系统无关。最终希望达到的目标是QCPF_Model.dll/QCPF_ViewModel.dll及QCPF_HostView.exe不再更新,更新的只是插件。
最后,祝你开发愉快,如果您对此有什么疑问,可以留言进行讨论。