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




    长风破浪会有时,直挂云帆济沧海!
    可通过下方链接找到博主
    https://www.cnblogs.com/judes/p/10875138.html
  • 相关阅读:
    [NOIP2018]:旅行(数据加强版)(基环树+搜索+乱搞) HEOI
    关于微服务网关
    管理 API 时要问的关键问题
    如何构建成功的微服务架构
    后端老是不写接口文档?说自己很忙?
    Nginx配置文件详解
    Keepalived 高可用
    Jenkins 调用 Shell 脚本
    Supervisor
    Harbor高可用实现基于haproxy
  • 原文地址:https://www.cnblogs.com/judes/p/13229092.html
Copyright © 2020-2023  润新知