CTK框架(九):插件间通信

目录

1.概述

2.主要接口和方法

3.通信方式

4.实现步骤

5.实现示例

5.1.类通信

5.2.信号槽通信

6.类通信和信号槽通信的区别

7.注意事项


1.概述

在CTK Plugin Framework中,插件间的通信主要通过EventAdmin服务来完成。EventAdmin是一种基于发布/订阅的通信方式,一个插件可以订阅某一主题的事件,而另一个插件则可以发布与该主题相关的事件,从而实现通信。

CTK框架中的事件监听,其实就是观察者模式,流程大概如下:

1)接收者注册监听事件(接收方想监听xxx信息)

2)发送者发送事件(发送方发送xxx信息)

3)接收者接收到事件并响应(接收方收到xxx事件后的动作)

相比调用插件接口,监听事件插件间依赖关系更弱,不用指定事件的接收方和发送方是谁。

2.主要接口和方法

1.通信主要用到了​​ctkEventAdmin​​结构体,主要定义了如下接口:

  • postEvent:以异步方式发送事件。
  • sendEvent:以同步方式发送事件。
  • publishSignal:通过信号与槽的方式发送事件。
  • unpublishSignal:取消发送事件。
  • subscribeSlot:通过信号与槽的方式订阅事件,并返回订阅的ID。
  • unsubscribeSlot:取消订阅事件。
  • updateProperties:更新某个订阅ID的主题。

2.通信是数据:​​ctkDictionary​

CTK插件间通信的数据主要通过ctkDictionary传递,它实际上是一个hash表(在Qt中通常使用QHash<QString, QVariant>实现),用于存储事件的属性。

3.通信方式

1. 类通信

类通信的原理是直接将信息使用CTK的eventAdmin接口sendpost出去。发送插件需要创建事件对象,并设置事件的属性(如主题、数据等),然后通过EventAdmin服务发送事件。接收插件则通过订阅相应主题的事件来接收信息,并在接收到事件时执行相应的处理函数。

2. 信号与槽通信

除了类通信外,CTK还支持通过Qt的信号与槽机制进行插件间通信。发送插件可以发射一个信号,该信号携带需要传递的信息;接收插件则连接该信号到一个槽函数上,当信号被发射时,槽函数会被调用,从而实现信息的传递和处理。

4.实现步骤

以发布博客为例,实现CTK插件间通信的大致步骤如下:

  1. 编译EventAdmin库 :确保eventadmin.dll(或其他平台对应的库文件)已经编译成功。
  2. 创建发送插件
    • 新建一个插件项目,编写发送类(如BlogManager),在该类中实现发布事件的方法(如publishBlog)。
    • 在发送类的构造函数中,通过ctkPluginContext获取EventAdmin服务的引用,并保存为成员变量。
    • 在发布事件的方法中,创建ctkEvent对象,设置事件的属性和数据,然后通过EventAdmin服务的sendEventpostEvent方法发送事件。
  3. 创建接收插件
    • 新建一个插件项目,编写接收类(如BlogEventHandler),实现ctkEventHandler接口中的handleEvent方法。
    • 在接收类的激活类中,通过ctkPluginContext注册服务,并订阅相应主题的事件。
    • handleEvent方法中编写处理事件的逻辑。
  4. 启用插件
    • 在主程序中加载并启动发送插件和接收插件。
    • 发送插件发布事件后,接收插件将接收到该事件,并执行相应的处理逻辑。

5.实现示例

5.1.类通信

代码结构:

插件结构说明:

cn.qtio.publisher:发送【发布者】

cn.qtio.subscriber:接收【订阅类】

发送【发布者】

publisheractivator.h

cpp 复制代码
#ifndef TESTPLUGINACTIVATOR_H
#define TESTPLUGINACTIVATOR_H

#include <QObject>
#include <ctkPluginActivator.h>

class PublisherActivator : public QObject, public ctkPluginActivator
{
    Q_OBJECT
    Q_INTERFACES(ctkPluginActivator)
    Q_PLUGIN_METADATA(IID "Publisher")

public:
    void start(ctkPluginContext* context) Q_DECL_OVERRIDE;
    void stop(ctkPluginContext* context) Q_DECL_OVERRIDE;
};

#endif // TESTPLUGINACTIVATOR_H

publisheractivator.cpp

cpp 复制代码
#include "publisheractivator.h"

#include <QDebug>
#include <QThread>
#include <service/event/ctkEventAdmin.h>

void PublisherActivator::start(ctkPluginContext *context)
{
    ctkServiceReference ref = context->getServiceReference<ctkEventAdmin>();
    ctkEventAdmin *eventAdmin = qobject_cast<ctkEventAdmin*>(context->getService(ref));
    ctkProperties props;
    props.insert("type", "消息类型");
    ctkEvent event("cn/qtio/eventAdmin/subscriber/handleEvent", props);
    if(eventAdmin){
        //sendEvent:同步通信
        eventAdmin->sendEvent(event);
    }
}

void PublisherActivator::stop(ctkPluginContext *context)
{
    Q_UNUSED(context)
}
#if (QT_VERSION < QT_VERSION_CHECK(5,0,0))
Q_EXPORT_PLUGIN2(Publisher, PublisherActivator)
#endif

接收【订阅类】

subscriberactivator.h

cpp 复制代码
#ifndef SUBSCRIBERACTIVATOR_H
#define SUBSCRIBERACTIVATOR_H

#include <QObject>
#include <ctkPluginActivator.h>
#include <service/event/ctkEventAdmin.h>
#include <service/event/ctkEventHandler.h>

class SubscriberActivator :
        public QObject,
        public ctkPluginActivator,
        public ctkEventHandler
{
    Q_OBJECT
    Q_INTERFACES(ctkPluginActivator)
    Q_INTERFACES(ctkEventHandler)
    Q_PLUGIN_METADATA(IID "Subscriber")

public:
    void start(ctkPluginContext* context) Q_DECL_OVERRIDE;
    void stop(ctkPluginContext* context) Q_DECL_OVERRIDE;

private:
    void handleEvent(const ctkEvent &event) Q_DECL_OVERRIDE;

private:
    ctkEventAdmin *m_eventAdmin;
};

#endif // SUBSCRIBERACTIVATOR_H

subscriberactivator.cpp

cpp 复制代码
#include "subscriberactivator.h"

#include <QDebug>
#include <service/event/ctkEventConstants.h>

void SubscriberActivator::start(ctkPluginContext* context)
{
	//事件管理服务
	ctkServiceReference eventAdminRef = context->getServiceReference<ctkEventAdmin>();
	m_eventAdmin = qobject_cast<ctkEventAdmin*>(context->getService(eventAdminRef));
	if (!m_eventAdmin) {
		qDebug() << "事件管理服务获取失败!";
	}

	//消息订阅
	ctkDictionary props;
	props.insert(ctkEventConstants::EVENT_TOPIC, "cn/qtio/eventAdmin/subscriber/handleEvent");
	context->registerService<ctkEventHandler>(this, props);
}

void SubscriberActivator::stop(ctkPluginContext* context)
{
	Q_UNUSED(context)
}

void SubscriberActivator::handleEvent(const ctkEvent& event)
{
	//查看消息包所有信息
	foreach(QString propertyName, event.getPropertyNames()) {
		qDebug() << "SubscriberActivator key:"
			<< propertyName
			<< "value:"
			<< event.getProperty(propertyName);
	}
}
#if (QT_VERSION < QT_VERSION_CHECK(5,0,0))
Q_EXPORT_PLUGIN2(Subscriber, SubscriberActivator)
#endif

5.2.信号槽通信

信号槽通信和Qt的信号槽绑定类似,就不在这里赘述了。

6.类通信和信号槽通信的区别

  1. 通过event事件通信,是直接调用CTK的接口,把数据发送到CTK框架;通过信号槽方式,会先在Qt的信号槽机制中转一次,再发送到CTK框架。故效率上来讲,event方式性能高于信号槽方式。

  2. 两种方式发送数据到CTK框架,这个数据包含:主题+属性。主题就是topic,属性就是ctkDictionary。 一定要注意signal方式的信号定义,参数不能是自定义的,一定要是ctkDictionary,不然会报信号槽参数异常错误。

  3. 两种方式可以混用,如发送event事件,再通过槽去接收;发送signal事件,再通过event是接收。

  4. 同步:sendEvent、Qt::DirectConnection;异步:postEvent、Qt::QueuedConnection

这里的同步是指:发送事件之后,订阅了这个主题的数据便会处理数据【handleEvent、slot】,处理的过程是在发送者的线程完成的。可以理解为在发送了某个事件之后,会立即执行所有订阅此事件的回调函数。

异步:发送事件之后,发送者便会返回不管,订阅了此事件的所有插件会根据自己的消息循环,轮到了处理事件后才会去处理。不过如果长时间没处理,CTK也有自己的超时机制。如果事件处理程序花费的时间比配置的超时时间长,那么就会被列入黑名单。一旦处理程序被列入黑名单,它就不会再被发送任何事件。

  1. handleEvent里是线程池里运行的,可以通过打印线程id测试出。

7.注意事项

  • 在进行插件间通信时,需要确保所有相关的库文件都已经正确编译并链接到项目中。
  • 订阅事件时,可以指定事件过滤器来过滤不需要处理的事件。
  • 发送事件时,需要确保事件的主题和数据已经正确设置,以便接收插件能够正确解析和处理。
相关推荐
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
励志成为嵌入式工程师3 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
记录成长java5 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
前端青山5 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
青花瓷5 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
睡觉谁叫~~~5 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
音徽编程5 小时前
Rust异步运行时框架tokio保姆级教程
开发语言·网络·rust