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

#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

#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

#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

#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也有自己的超时机制。如果事件处理程序花费的时间比配置的超时时间长,那么就会被列入黑名单。一旦处理程序被列入黑名单,它就不会再被发送任何事件。

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

7.注意事项

  • 在进行插件间通信时,需要确保所有相关的库文件都已经正确编译并链接到项目中。
  • 订阅事件时,可以指定事件过滤器来过滤不需要处理的事件。
  • 发送事件时,需要确保事件的主题和数据已经正确设置,以便接收插件能够正确解析和处理。
  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值