logo头像
Snippet 博客主题

QuikPlatform Demo说明

QuikPaltformTest目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
QuikPaltformTest
|--> bin // 打包后可执行程序
|--> doc // 文档
|--> trunk
        |--> bin // 平台的动态库
     |--> include // 平台的头文件
                  |--> CoreApp
                 |--> CoreLib
                  |--> CoreUi
                  |--> KIQtGui
                  |--> MyUI
                  |--> server
                  |--> server_imp
                  |--> ToolsLib
         |--> lib // 平台的静态库
         |--> src // 自定义源码
                  |--> plugins // 自定义插件
                          |--> MyUI
                          |--> PluginGreetServer
                          |--> PluginUIA
                          |--> PluginUIB
                          |--> PluginUIC
                  |--> QuikPlatformIDE // 工程目录
                          |--> QuikPlatformIDE.sln // vs启动文件
                          |--> ...

创建插件

QuikPaltform平台封装了一套插件机制,通过开发各种功能插件组装起来形成一个软件。软件是分层结构,底层是quikpaltform平台,实现一些公共操作和框架代码,其上则是各种功能插件。插件分为UI插件、Server插件,他们遵循的规范都大同小异,分为声明接口、实现接口、注册这3个步骤。下面以Demo中”SayHello”服务接口为例:

1.声明对外提供的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// IGreetServer.h
...
namespace Kcc { namespace Greet {
class IGreetServer {
public:
virtual void SayHello() = 0; // 对外提供的接口
virtual ~IGreetServer() {}
};
typedef QSharedPointer<IGreetServer> PIGreetServer; // 接口对象的智能指针

// 插件定义的通知事件类型
enum {
Notify_SayHello = 1
};
}}
...

2.实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// GreetSever.h
...
/*如果想构建自己的服务,必须先继承ServerBase和自己自定义的服务接口(IGreetServer)*/
class GreetServer : public ServerBase, public IGreetServer
{
public:
GreetServer();
~GreetServer();

public:
// IServerInterfaceBase里的这几个接口必须要实现
virtual QString GetServerGroupName() const { return SERVER_GROUP_IGREETSERVER_NAME; }
virtual QString GetInterfaceDefName() const { return SERVER_INTERFACE_IGREETSERVER_NAME; }
virtual unsigned int GetVersion() const { return SERVER_VERSION; }

public:
// IGreetServer
virtual void SayHello();
};
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// GreetSever.cpp
...
void GreetServer::SayHello()
{
//输出log日志
LOGOUT(QString("GreetServer SayHello"), LOG_NORMAL);

//1.发送通知:先请求对应的服务,这个服务对象类型为必须是PIServerInterfaceBase
PIServerInterfaceBase pserver = RequestServer(SERVER_GROUP_IGREETSERVER_NAME, SERVER_INTERFACE_IGREETSERVER_NAME);

//2.确定发送参数,发送命令(Notify_SayHello)是必须的,参数可以根据实际情况判断是否需要
NotifyStruct notifyStruct;
notifyStruct.code = Notify_SayHello;
notifyStruct.paramMap["Sender"] = "come from Greetserver";

//3.发送通知
if (pserver)
{
pserver->emitNotify(notifyStruct);
}
}
...

3.插件注册

1
2
3
4
5
6
7
8
9
10
11
12
// PluginGreetServer.h
...
//每个被注册的模块都必须继承Module
class PluginGreetServer : public Module
{
//每个模块都必须使用此宏
DEFINE_MODULE
public:
PluginGreetServer(QString strName);
~PluginGreetServer();
};
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
// 静态注册(当dll被加载时会调用),每个模块都必须使用此宏(第一个参数是当前类的类型;第二个是组的名称,一般为空;第三个是显示名称,一般是类型名称)
REG_MODULE_BEGIN(PluginGreetServer, "", "PluginGreetServer")
REG_MODULE_END(PluginGreetServer)

//每个Module的参数一般情况为Module_Type_Normal,根据实际情况而定
PluginGreetServer::PluginGreetServer(QString strName):Module(Module_Type_Normal, strName)
{
/*
注册问候服务(服务都必须在构造的时候注册,请求服务一般都在所有模块构造之后,这样可以确保所有服务都已经被注册,
可以在每个模块的init()函数中请求,因为这个函数会在所有模块被构造之后被调用)
*/
RegServer<GreetServer>(new GreetServer());
}
...

使用插件

1. 通过直接获取插件对象指针来调用其接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// PluginUIB.h
...
class PluginUIB :public QWidget, public Module
{
Q_OBJECT
DEFINE_MODULE
public:
PluginUIB(QString strName);
~PluginUIB();

public: //Module
virtual void init(KeyValueMap ¶ms);
virtual void unInit(KeyValueMap &saveParams);

private:
PIGreetServer m_pGreetServer; // 问候服务接口
};
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// PluginUIB.cpp
...
void PluginUIB::init(KeyValueMap ¶ms)
{
// 1. 获取要使用插件对象
m_pGreetServer = RequestServer<IGreetServer>(SERVER_GROUP_IGREETSERVER_NAME, SERVER_INTERFACE_IGREETSERVER_NAME);
if (!m_pGreetServer)
{
LOGOUT("IGreetServer未注册", LOG_ERROR);
}
}

void PluginUIB::unInit(KeyValueMap &saveParams)
{
// 3. 使用插件对象释放
m_pGreetServer.clear();
}

// 槽函数
void PluginUIB::onPluginBTest()
{
ui.textEdit->append("面板B的按钮已经被按下");
if (m_pGreetServer)
{
// 2. 调用插件对象对外提供的接口
m_pGreetServer->SayHello();
}
}

2. 通过消息发布/订阅的形式使用插件接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// PluginUIC.cpp
// 订阅消息(消息接收方)
void PluginUIC::init(KeyValueMap &params)
{
// 1.请求问候服务
PIServerInterfaceBase pGreetserver = RequestServer(SERVER_GROUP_IGREETSERVER_NAME, SERVER_INTERFACE_IGREETSERVER_NAME);
// 2.连接槽函数
pGreetserver->connectNotify(Notify_SayHello, this, SLOT(onRecieveGreetMsg(unsigned int, const NotifyStruct&)));
}

// 3. 槽函数消息响应
void PluginUIC::onRecieveGreetMsg(unsigned int code, const NotifyStruct& param)
{
if (code == Notify_SayHello)
{
QString strGreetMsg = param.paramMap["Sender"].toString();
ui.textEdit->append(strGreetMsg);
}
QMessageBox::about(this, tr("测试双击菜单"), tr("双击响应!"));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// GreetSever.cpp
// 发布消息(消息发送方)
void GreetServer::SayHello()
{
//输出log日志
LOGOUT(QString("GreetServer SayHello"), LOG_NORMAL);

//1.发送通知:先请求对应的服务,这个服务对象类型为必须是PIServerInterfaceBase
PIServerInterfaceBase pserver = RequestServer(SERVER_GROUP_IGREETSERVER_NAME, SERVER_INTERFACE_IGREETSERVER_NAME);

//2.确定发送参数,发送命令(Notify_SayHello)是必须的,参数可以根据实际情况判断是否需要
NotifyStruct notifyStruct;
notifyStruct.code = Notify_SayHello;
notifyStruct.paramMap["Sender"] = "come from Greetserver";

//3.发送通知
if (pserver)
{
pserver->emitNotify(notifyStruct);
}
}

补充(2023/6/20)

最新平台框架对插件的使用有些更新,更加简化了。但是大体原理还是一样,这里稍微描述下原理流程:

  1. 首先软件分为两层,平台层和插件层。平台层实现公共操作和框架,独立打包,安装到环境变量中,插件层在平台之上,实现具体功能模块。
  2. 插件内部对象采用纯虚函数定义对外功能接口,内部实现,即插件的功能逻辑。
  3. 插件外部对象用__declspec(dllexport)导出dll,是对外的唯一入口,由平台加载,检测。外部对象构造时会new内部对象,然后记录下来。
  4. 对于平台加载过程: 平台 –> 加载dll,构造插件外部对象并初始化 –> 构造插件内部对象并初始化。
  5. 对于使用者流程是: 向平台请求获取插件内部对象的指针,然后就可以访问内部对象实现的接口了。