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也有自己的超時機制。如果事件處理程序花費的時間比配置的超時時間長,那么就會被列入黑名單。一旦處理程序被列入黑名單,它就不會再被發送任何事件。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM