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测试出

posted @ 2020-07-03 10:52  朱小勇  阅读(4076)  评论(0编辑  收藏  举报