CTK-插件间通信原理
零、概述
1、通信主要用到了ctkEventAdmin结构体,主要定义了如下接口:
postEvent:类通信形式异步发送事件
sendEvent:类通信形式同步发送事件
publishSignal:信号与槽通信形式发送事件
unpublishSignal:取消发送事件
subscribeSlot:信号与槽通信形式订阅时间,返回订阅的ID
unsubscribeSlot:取消订阅事件
updateProperties:更新某个订阅ID的主题
2、通信的数据是:ctkDictionary
其实就是个hash表:
一、类通信
原理就是直接将信息使用ctk的eventAdmin接口send/post出去
1.1、发送插件
①、新建qmake工程,EventHandleSender
文件结构,third_libs存放的是ctk相关的头文件和动态库
②、新建发送类
blog_manager.h
#ifndef BLOG_MANAGER_H #define BLOG_MANAGER_H #include <ctkPluginContext.h> typedef struct Blog_Info { QString title; QString author; QString content; } Blog; class BlogManager { public: BlogManager(ctkPluginContext* context); // 发布事件 void publishBlog(const Blog& blog); private: ctkPluginContext* m_pContext; }; #endif // BLOG_MANAGER_H
blog_manager.cpp
#include "blog_manager.h" #include <service/event/ctkEventAdmin.h> #include <QtDebug> BlogManager::BlogManager(ctkPluginContext* context) : m_pContext(context) { } // 发布事件 void BlogManager::publishBlog(const Blog& blog) { ctkServiceReference ref = m_pContext->getServiceReference<ctkEventAdmin>(); if (ref) { ctkEventAdmin* eventAdmin = m_pContext->getService<ctkEventAdmin>(ref); ctkDictionary props; props["title"] = blog.title; props["content"] = blog.content; props["author"] = blog.author; ctkEvent event("org/commontk/bloggenerator/published", props); qDebug() << "Publisher sends a message, properties:" << props; eventAdmin->sendEvent(event); } }
③、新建激活类
activator.h
#ifndef ACTIVATOR_H #define ACTIVATOR_H #include <QObject> #include "blog_manager.h" #include "ctkPluginActivator.h" #include "ctkPluginContext.h" class Activator: public QObject, public ctkPluginActivator { Q_OBJECT Q_INTERFACES(ctkPluginActivator) Q_PLUGIN_METADATA(IID "BlogManager") public: Activator(); void start(ctkPluginContext *context); void stop(ctkPluginContext *context); private: BlogManager *m_pBlogManager; }; #endif // ACTIVATOR_H
activator.cpp
#include "activator.h" #include <QDebug> Activator::Activator() { } void Activator::start(ctkPluginContext *context) { qDebug()<<"start sender"; m_pBlogManager = new BlogManager(context); Blog blog; blog.title = "CTK Event Admin"; blog.content = "This is a simple blog"; blog.author = "jude"; m_pBlogManager->publishBlog(blog); } void Activator::stop(ctkPluginContext *context) { Q_UNUSED(context) delete m_pBlogManager; }
④、.pro文件
QT += core QT -= gui TARGET = EventHandleSender TEMPLATE = lib # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 CONFIG += c++11 HEADERS += \ blog_manager.h \ activator.h SOURCES += blog_manager.cpp \ activator.cpp RESOURCES += \ resource.qrc # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target # CTK源码路径 INCLUDEPATH += $$PWD/third_libs/ctk/include/CTK_src/Core \ += $$PWD/third_libs/ctk/include/CTK_src/PluginFramework # CTK安装路径 INCLUDEPATH += $$PWD/third_libs/ctk/include/CTK_install/Core \ += $$PWD/third_libs/ctk/include/CTK_install/PluginFramework # CTK库路径 LIBS += -L$$PWD/third_libs/ctk/libs -lCTKCore -lCTKPluginFramework
1.2、接收插件
①、新建qmake工程,EventHandleRcvr
②、新建接收类
blog_event_handler.h
#ifndef BLOG_EVENT_HANDLER_H #define BLOG_EVENT_HANDLER_H #include <QObject> #include <service/event/ctkEventHandler.h> // 事件处理程序(或订阅者) class BlogEventHandler : public QObject, public ctkEventHandler { Q_OBJECT Q_INTERFACES(ctkEventHandler) public: // 处理事件 void handleEvent(const ctkEvent& event) Q_DECL_OVERRIDE { QString title = event.getProperty("title").toString(); QString content = event.getProperty("content").toString(); QString author = event.getProperty("author").toString(); qDebug() << "EventHandler received the message, topic:" << event.getTopic() << "properties:" << "title:" << title << "content:" << content << "author:" << author; } }; #endif // BLOG_EVENT_HANDLER_H
③、新建激活类
activator.h
#ifndef ACTIVATOR_H #define ACTIVATOR_H #include <QObject> #include "blog_event_handler.h" #include "ctkPluginActivator.h" #include "ctkPluginContext.h" class Activator: public QObject, public ctkPluginActivator { Q_OBJECT Q_INTERFACES(ctkPluginActivator) Q_PLUGIN_METADATA(IID "BLOG_EVENT_HANDLER") public: Activator(); void start(ctkPluginContext *context); void stop(ctkPluginContext *context); private: BlogEventHandler *m_pEventHandler; }; #endif // ACTIVATOR_H
activator.cpp
#include "activator.h" #include "blog_event_handler.h" #include <service/event/ctkEventConstants.h> #include <QtDebug> Activator::Activator() { } void Activator::start(ctkPluginContext *context) { qDebug()<<"start rcvr"; m_pEventHandler = new BlogEventHandler(); ctkDictionary props; props[ctkEventConstants::EVENT_TOPIC] = "org/commontk/bloggenerator/published"; props[ctkEventConstants::EVENT_FILTER] = "(author=jude)"; context->registerService<ctkEventHandler>(m_pEventHandler, props); } void Activator::stop(ctkPluginContext *context) { Q_UNUSED(context) delete m_pEventHandler; }
注意:这里是将m_pEventHandler注册成了ctkEventHandler服务,必须要注册成这个才能实现通信。那么问题来了,如果我既希望把BlogEventHandler作为通信的工具【注册成ctkEventHandler】,又希望它作为服务能被其他插件使用,那么该怎么办呢【因为其他地方查找服务是通过接口类的名字来的,这里并没有注册成接口类】:可以把一个插件注册成多个服务:
④、.pro文件
QT += core QT -= gui TARGET = EventHandleRcvr TEMPLATE = lib # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 CONFIG += c++11 # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target # CTK源码路径 INCLUDEPATH += $$PWD/third_libs/ctk/include/CTK_src/Core \ += $$PWD/third_libs/ctk/include/CTK_src/PluginFramework # CTK安装路径 INCLUDEPATH += $$PWD/third_libs/ctk/include/CTK_install/Core \ += $$PWD/third_libs/ctk/include/CTK_install/PluginFramework # CTK库路径 LIBS += -L$$PWD/third_libs/ctk/libs -lCTKCore -lCTKPluginFramework HEADERS += \ blog_event_handler.h \ activator.h SOURCES += \ activator.cpp RESOURCES += \ resource.qrc
1.3、新建程序入口项目
①、项目结构
②、main.cpp
#include <QApplication> #include <ctkPluginFrameworkFactory.h> #include <ctkPluginFramework.h> #include <ctkPluginException.h> #include <ctkPluginContext.h> #include <QtDebug> #include <QUrl> #include <QDialog> #include <QDir> #include "abslogservice.h" #include "blog_event_handler.h" #include "blog_manager.h" #if 1 #include <ctkPluginFrameworkLauncher.h> #include <ctkPluginContext.h> #include <ctkPluginException.h> QString static pluginPath1 = "C:/Users/Administrator/Desktop/myctk2017/EventHandleRcvr/debug/EventHandleRcvr.dll"; QString static pluginPath2 = "C:/Users/Administrator/Desktop/myctk2017/EventHandleSender/debug/EventHandleSender.dll"; #endif QString static pluginPath = QDir::currentPath()+"/third_libs/plugin/libs/ctk-plugin-first.dll";//最简单的日志插件 int main(int argc, char *argv[]) { QApplication a(argc, argv); // 获取插件所在位置 QString path = QDir::currentPath() + "/third_libs/ctk/libs"; // 在插件的搜索路径列表中添加一条路径 ctkPluginFrameworkLauncher::addSearchPath(path); ctkPluginFrameworkLauncher::start("org.commontk.eventadmin"); // 获取插件上下文 ctkPluginContext* context = ctkPluginFrameworkLauncher::getPluginContext(); // 启动插件 BlogEventHandler try { QSharedPointer<ctkPlugin> plugin = context->installPlugin(QUrl::fromLocalFile(pluginPath1)); plugin->start(ctkPlugin::START_TRANSIENT); qDebug() << "BlogEventHandler start ..."; } catch (const ctkPluginException &e) { qDebug() << "Failed to start BlogEventHandler" << e.what(); } // 启动插件 BlogManager try { QSharedPointer<ctkPlugin> plugin = context->installPlugin(QUrl::fromLocalFile(pluginPath2)); plugin->start(ctkPlugin::START_TRANSIENT); qDebug() << "BlogManager start ..."; } catch (const ctkPluginException &e) { qDebug() << "Failed to start BlogManager" << e.what(); } // 停止插件 ctkPluginFrameworkLauncher::stop(); return a.exec(); }
③、.pro
#------------------------------------------------- # # Project created by QtCreator 2020-07-02T18:12:37 # #------------------------------------------------- QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = CtkFramework TEMPLATE = app # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 CONFIG += c++11 SOURCES += \ main.cpp # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target # CTK源码路径 INCLUDEPATH += $$PWD/third_libs/ctk/include/CTK_src/Core \ += $$PWD/third_libs/ctk/include/CTK_src/PluginFramework # CTK安装路径 INCLUDEPATH += $$PWD/third_libs/ctk/include/CTK_install/Core \ += $$PWD/third_libs/ctk/include/CTK_install/PluginFramework # CTK库路径 LIBS += -L$$PWD/third_libs/ctk/libs -lCTKCore -lCTKPluginFramework # 插件头文件路径 INCLUDEPATH += $$PWD/third_libs/plugin/include
二、信号槽通信
原理是将Qt自己的信号与ctk的发送事件绑定、槽与事件订阅绑定
2.1、新建发送项目
①、新建发送类
signal.h
#ifndef SIGNAL_H #define SIGNAL_H #include <QObject> #include "service/event/ctkEventAdmin.h" #include "ctkPluginContext.h" class Signal : public QObject { Q_OBJECT public: Signal(ctkPluginContext* context); public: void send(); signals: void sendToCtkSignal(const ctkDictionary&); private: ctkPluginContext* context; }; #endif // SIGNAL_H
signal.cpp
#include "signal.h" #include "ctkServiceReference.h" #include <QDebug> Signal::Signal(ctkPluginContext* context) :context(context) { ctkServiceReference ref = context->getServiceReference<ctkEventAdmin>(); if(ref) { ctkEventAdmin* evenAdmin = context->getService<ctkEventAdmin>(ref); evenAdmin->publishSignal(this,SIGNAL(sendToCtkSignal(ctkDictionary)),"judesmorning/signal",Qt::QueuedConnection); } } void Signal::send() { ctkDictionary info; info["info"] = "info by ctk signal"; emit sendToCtkSignal(info); qDebug()<<"signal plugin send info:"<<info["info"]; }
②、新建激活类
activator.h
#ifndef ACTIVATOR_H #define ACTIVATOR_H #include <QObject> #include "signal.h" #include "ctkPluginActivator.h" #include "ctkPluginContext.h" class Activator : public QObject,public ctkPluginActivator { Q_OBJECT Q_INTERFACES(ctkPluginActivator) Q_PLUGIN_METADATA(IID "BLOG_MANAGER_USING_SINGALS") public: Activator(); void start(ctkPluginContext* context) override; void stop(ctkPluginContext* context) override; private: QScopedPointer<Signal> signal; }; #endif // ACTIVATOR_H
activator.cpp
#include "activator.h" Activator::Activator() { } void Activator::start(ctkPluginContext *context) { signal.reset(new Signal(context)); signal->send(); } void Activator::stop(ctkPluginContext *context) { Q_UNUSED(context); }
③、工程配置
.pro
# 当前工程用于生成插件 DEFINES += CREATE_PLUGIN # 当前工程用于使用插件 #DEFINES += USE_PLUGIN include($$PWD/third_libs/ctk.pri) HEADERS += \ signal.h \ activator.h SOURCES += \ signal.cpp \ activator.cpp RESOURCES += \ resource.qrc
.pri
# 生成文件名 TARGET = ctksignalplugin # CTK源码路径 INCLUDEPATH += $$PWD/ctk/include/CTK_src/Core \ += $$PWD/ctk/include/CTK_src/PluginFramework # CTK安装路径 INCLUDEPATH += $$PWD/ctk/include/CTK_install/Core \ += $$PWD/ctk/include/CTK_install/PluginFramework # CTK库路径 LIBS += -L$$PWD/ctk/libs -lCTKCore -lCTKPluginFramework if(contains(DEFINES,CREATE_PLUGIN)){ message('this is create ctk plugin project.') QT += core QT -= gui TEMPLATE = lib } if(contains(DEFINES,USE_PLUGIN)){ message('this is use ctk plugin project.') # 插件头文件路径 INCLUDEPATH += $$PWD/plugin/include QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = app } # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target
2.2、新建接收项目
①、新建发送类
slot.h
#ifndef SLOT_H #define SLOT_H #include <QObject> #include "service/event/ctkEventAdmin.h" #include "ctkPluginContext.h" class Slot : public QObject { Q_OBJECT public: Slot(ctkPluginContext* context); public slots: void rcvFromCtkSlot(const ctkEvent& event); private: ctkPluginContext* context; }; #endif // SLOT_H
slot.cpp
#include "slot.h" #include "service/event/ctkEventConstants.h" Slot::Slot(ctkPluginContext* context) : context(context) { ctkDictionary props; props[ctkEventConstants::EVENT_TOPIC] = "judesmorning/signal"; ctkServiceReference ref = context->getServiceReference<ctkEventAdmin>(); if (ref) { ctkEventAdmin* eventAdmin = context->getService<ctkEventAdmin>(ref); eventAdmin->subscribeSlot(this,SLOT(rcvFromCtkSlot(ctkEvent)), props, Qt::QueuedConnection); } } void Slot::rcvFromCtkSlot(const ctkEvent& event) { qDebug()<<"slot plugin rcv info:"<<event.getProperty("info").toString(); }
②、新建激活类
activator.h
#ifndef ACTIVATOR_H #define ACTIVATOR_H #include <QObject> #include "ctkPluginContext.h" #include "ctkPluginActivator.h" #include "slot.h" class Activator : public QObject, public ctkPluginActivator { Q_OBJECT Q_INTERFACES(ctkPluginActivator) Q_PLUGIN_METADATA(IID "BLOG_MANAGER_USING_SLOT") public: Activator(); void start(ctkPluginContext* context) override; void stop(ctkPluginContext* context) override; private: QScopedPointer<Slot> slot; }; #endif // ACTIVATOR_H
activator.cpp
#include "activator.h" Activator::Activator() { } void Activator::start(ctkPluginContext *context) { slot.reset(new Slot(context)); } void Activator::stop(ctkPluginContext *context) { Q_UNUSED(context); }
③、工程配置
.pro
# 当前工程用于生成插件 DEFINES += CREATE_PLUGIN # 当前工程用于使用插件 #DEFINES += USE_PLUGIN include($$PWD/third_libs/ctk.pri) HEADERS += \ slot.h \ activator.h SOURCES += \ slot.cpp \ activator.cpp RESOURCES += \ resource.qrc]
.pri
# 生成文件名 TARGET = ctkslotplugin # CTK源码路径 INCLUDEPATH += $$PWD/ctk/include/CTK_src/Core \ += $$PWD/ctk/include/CTK_src/PluginFramework # CTK安装路径 INCLUDEPATH += $$PWD/ctk/include/CTK_install/Core \ += $$PWD/ctk/include/CTK_install/PluginFramework # CTK库路径 LIBS += -L$$PWD/ctk/libs -lCTKCore -lCTKPluginFramework if(contains(DEFINES,CREATE_PLUGIN)){ message('this is create ctk plugin project.') QT += core QT -= gui TEMPLATE = lib } if(contains(DEFINES,USE_PLUGIN)){ message('this is use ctk plugin project.') # 插件头文件路径 INCLUDEPATH += $$PWD/plugin/include QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = app } # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target
ps:
1、通过event事件通信,是直接调用ctk的接口,把数据发送到ctk框架;通过信号槽方式,会先在Qt的信号槽机制中转一次,再发送到ctk框架。故效率上来讲,event方式性能高于信号槽方式。
2、两种方式发送数据到ctk框架,这个数据包含:主题+属性。主题就是topic,属性就是ctkDictionary。
event方式
signal方式
一定要注意signal方式的信号定义,参数不能是自定义的,一定要是ctkDictionary,不然会报信号槽参数异常错误
3、两种方式可以混用,如发送event事件,再通过槽去接收;发送signal事件,再通过event是接收。
4、同步:sendEvent、Qt::DirectConnection;异步:postEvent、Qt::QueuedConnection
这里的同步是指:发送事件之后,订阅了这个主题的数据便会处理数据【handleEvent、slot】,处理的过程是在发送者的线程完成的。可以理解为在发送了某个事件之后,会立即执行所有订阅此事件的回调函数。
异步:发送事件之后,发送者便会返回不管,订阅了此事件的所有插件会根据自己的消息循环,轮到了处理事件后才会去处理。不过如果长时间没处理,ctk也有自己的超时机制。如果事件处理程序花费的时间比配置的超时时间长,那么就会被列入黑名单。一旦处理程序被列入黑名单,它就不会再被发送任何事件。
5、handleEvent里是线程池里运行的,可以通过打印线程id测试出
长风破浪会有时,直挂云帆济沧海!
可通过下方链接找到博主
https://www.cnblogs.com/judes/p/10875138.html