Qt基于CTK Plugin Framework搭建插件框架--插件通信【注册接口调用】

news2024/11/24 20:19:52

文章目录

  • 一、前言
  • 二、插件完善
    • 2.1、添加接口文件
    • 2.2、添加接口实现类
    • 2.3、服务注册(Activator注册服务)
  • 三、接口调用
  • 四、接口 - 插件 - 服务的关系
    • 4.1、1对1
    • 4.2、多对1
    • 4.3、1对多

一、前言

通过Qt基于CTK Plugin Framework搭建插件框架–创建插件一文,我们知道了CTK创建插件的基本流程,但是在这篇文章中,我们只是创建了一个空插件,一个只有激活类的插件,没有任何功能。

一个CTK标准插件应该包含有:接口类、接口实现类、激活类;

  • 接口类就只做接口声明;
  • 实现类就只实现接口;
  • 激活类就负责将服务整合到ctk框架中;

二、插件完善

2.1、添加接口文件

首先我们需要确定插件向外部暴露的功能有什么,例如:添加说“Hello,CTK!”的操作

添加接口文件【HelloService.h】(通常是一个C++头文件,一个虚基类)

在这里插入图片描述

【HelloService.h】中添加如下代码

#ifndef HELLO_SERVICE_H
#define HELLO_SERVICE_H

#include <QtPlugin>

class HelloService
{
public:
    virtual ~HelloService() {}
    virtual void sayHello() = 0;
};

#define HelloService_iid "org.commontk.service.demos.HelloService"
Q_DECLARE_INTERFACE(HelloService, HelloService_iid)
//此宏将当前这个接口类声明为接口,后面的一长串就是这个接口的唯一标识。
#endif // HELLO_SERVICE_H

在这里插入图片描述


2.2、添加接口实现类

添加接口实现类【HelloImpl】

#ifndef HELLO_IMPL_H
#define HELLO_IMPL_H

#include "HelloService.h"
#include <QObject>

class ctkPluginContext;

class HelloImpl : public QObject, public HelloService
{
    Q_OBJECT
    Q_INTERFACES(HelloService)
    /*
    此宏与Q_DECLARE_INTERFACE宏配合使用。
    Q_DECLARE_INTERFACE:声明一个接口类
    Q_INTERFACES:当一个类继承这个接口类,表明需要实现这个接口类
    */

public:
    HelloImpl(ctkPluginContext* context);
    void sayHello() Q_DECL_OVERRIDE;
};

#endif // HELLO_IMPL_H

#include "hello_impl.h"
#include <ctkPluginContext.h>
#include <QtDebug>

HelloImpl::HelloImpl(ctkPluginContext* context)
{

}

void HelloImpl::sayHello()
{
    qDebug() << "Hello,CTK!";
}


2.3、服务注册(Activator注册服务)

激活类里有一个独占智能指针,指向接口类【使用多态,指针都指向父类】,然后在start里new一个实现类,注册这个实现类为服务,功能是实现接口类的接口,然后将智能指针指向这个实现类。

可以理解为以后向框架索取这个服务的时候,实际获取的就是这个new出来的实现类。如果不用智能指针,就需要在stop里手动delete这个实现类。

在这里插入图片描述
在这里插入图片描述


三、接口调用

插件启用后,就可以调用接口了

#include "mainwindow.h"

#include <QApplication>

#include "ctkPluginFrameworkFactory.h"
#include "ctkPluginFramework.h"
#include "ctkPluginException.h"
#include "ctkPluginContext.h"
#include <QDebug>

#include "../HelloCTK/HelloService.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    a.setApplicationName("CTK_PluginFramework");//给框架创建名称,Linux下没有会报错


    ctkPluginFrameworkFactory frameworkFactory;
    QSharedPointer<ctkPluginFramework> framework = frameworkFactory.getFramework();

    // 初始化并启动插件框架
    try {
        framework->init();
        framework->start();
        qDebug() << "======================================";
        qDebug() << "CTK plugin framework start...";
        qDebug() << "======================================";
    } catch (const ctkPluginException &e) {
        qDebug() << "CTK plugin framework init err: " << e.what();
        return -1;
    }

    // 获取插件服务的contex
    ctkPluginContext* pluginContext = framework->getPluginContext();
    try {
        // 安装插件
        QString HelloCTK_dir = "C:/Qt_Pro/build-CTK_PluginFramework-CMake-Debug/HelloCTK/bin/plugins/HelloCTK.dll";
        QSharedPointer<ctkPlugin> plugin = pluginContext->installPlugin(QUrl::fromLocalFile(HelloCTK_dir));
        qDebug() << QString("Plugin[%1_%2] installed...").arg(plugin->getSymbolicName()).arg(plugin->getVersion().toString());
        // 启动插件
        plugin->start(ctkPlugin::START_TRANSIENT);
        qDebug() << QString("Plugin[%1_%2] started").arg(plugin->getSymbolicName()).arg(plugin->getVersion().toString());
    } catch (const ctkPluginException &e) {
        qDebug() << QString("Failed install or run plugin: ") << e.what();
        return -2;
    }

    // 获取服务引用
    ctkServiceReference reference = pluginContext->getServiceReference<HelloService>();
    if (reference) {
        // 获取指定 ctkServiceReference 引用的服务对象
        //HelloService* service = qobject_cast<HelloService *>(pluginContext->getService(reference));
        HelloService* service = pluginContext->getService<HelloService>(reference);
        if (service != Q_NULLPTR) {
            // 调用服务
            service->sayHello();
        }
    }

    MainWindow w;
    w.show();

    return a.exec();
}

在这里插入图片描述
在这里插入图片描述
服务就是接口的实例,每生成一个服务就会调用一次注册器的start。把接口当做类,服务是根据类new出的对象,插件就是动态库dll。


四、接口 - 插件 - 服务的关系

4.1、1对1

1个接口类由1个类实现,输出1个服务和1个插件

上面项目为典型的1对1关系;


4.2、多对1

1个类实现多个接口类,输出多个服务和1个插件

无论像使用哪个服务最终都通过这同一插件来实现;

接口类1

#include <QtPlugin>

class Service1
{
public:
    virtual ~Service1() {}
    virtual void sayHello() = 0;
};

#define Service1_iid "org.commontk.service.demos.Service1"
Q_DECLARE_INTERFACE(Service1, Service1_iid)
//此宏将当前这个接口类声明为接口,后面的一长串就是这个接口的唯一标识。

接口类2

#include <QtPlugin>

class Service2
{
public:
    virtual ~Service2() {}
    virtual void sayBye() = 0;
};

#define Service2_iid "org.commontk.service.demos.Service2"
Q_DECLARE_INTERFACE(Service2, Service2_iid)
//此宏将当前这个接口类声明为接口,后面的一长串就是这个接口的唯一标识。

实现类:实现多个接口

#include "Service1.h"
#include "Service2.h"
#include <QObject>

class ctkPluginContext;

class HelloImpl : public QObject, public Service1, public Service2
{
    Q_OBJECT
    Q_INTERFACES(Service1)
    Q_INTERFACES(Service2)
    /*
    此宏与Q_DECLARE_INTERFACE宏配合使用。
    Q_DECLARE_INTERFACE:声明一个接口类
    Q_INTERFACES:当一个类继承这个接口类,表明需要实现这个接口类
    */

public:
    HelloImpl(ctkPluginContext* context);
    void sayHello() Q_DECL_OVERRIDE;
    void sayBye() Q_DECL_OVERRIDE;
};

#include "hello_impl.h"
#include <QtDebug>

HelloImpl::HelloImpl(ctkPluginContext* context)
{

}

void HelloImpl::sayHello()
{
    qDebug() << "Hello,CTK!";
}

void HelloImpl::sayBye()
{
    qDebug() << "Bye Bye,CTK!";
}

获取不同服务

// 获取服务引用
ctkServiceReference ref = context->getServiceReference<Service1>();
if (ref) {
    Service1* service = qobject_cast<Service1 *>(context->getService(ref));
    if (service != Q_NULLPTR)
        service->sayHello();
}

ref = context->getServiceReference<Service2>();
if (ref) {
    Service2* service = qobject_cast<Service2 *>(context->getService(ref));
    if (service != Q_NULLPTR)
        service->sayBye();
}

4.3、1对多

1个接口类,多个实现类,输出1个服务和多个插件

也就是某一个问题提供了多种解决思路,可以将接口类理解为一个问题,实现类则是解决思路;
例如,接口类有个接口,需要说:你好!实现类可以是中文说、也可以是英语说、也可以是法语说、也可以是俄语说,…

通过ctkPluginConstants::SERVICE_RANKINGctkPluginConstants::SERVICE_ID来调用不同的插件

虽然有多个插件,但都是被编译到同一个dll中。

服务的获取策略如下:

  • 容器会返回排行最低的服务,返回注册时SERVICE_RANKING属性值最小的服务;
  • 如果有多个服务的排行值相等,那么容器将返回PID值最小的那个服务;

某插件每次调用另一个插件的时候,只会生成一个实例,然后把实例存到内存当中,不会因为多次调用而生成多个服务实例。

接口类

#include <QtPlugin>

class Service
{
public:
    virtual ~Service() {}
    virtual void welcome() = 0;
};

#define Service_iid "org.commontk.service.demos.Service"
Q_DECLARE_INTERFACE(Service, Service_iid)
//此宏将当前这个接口类声明为接口,后面的一长串就是这个接口的唯一标识。

实现类1和激活类1

#include "Service.h"
#include <QObject>

class ctkPluginContext;

class WelcomeCTKImpl : public QObject, public Service
{
    Q_OBJECT
    Q_INTERFACES(Service)
    /*
    此宏与Q_DECLARE_INTERFACE宏配合使用。
    Q_DECLARE_INTERFACE:声明一个接口类
    Q_INTERFACES:当一个类继承这个接口类,表明需要实现这个接口类
    */

public:
    WelcomeCTKImpl(ctkPluginContext* context);
    void welcome() Q_DECL_OVERRIDE;
};

#include "WelcomeCTKImpl.h"
#include <QtDebug>

WelcomeCTKImpl::WelcomeCTKImpl(ctkPluginContext* context)
{

}

void WelcomeCTKImpl::welcome()
{
    qDebug() << "Welcome,CTK!";
}

void WelcomeCTKActivator::start(ctkPluginContext* context)
{
    ctkDictionary properties;
    properties.insert(ctkPluginConstants::SERVICE_RANKING, 2);
    properties.insert("name", "CTK");

    m_pImpl = new WelcomeCTKImpl();
    context->registerService<WelcomeService>(m_pImpl, properties);
}

void WelcomeCTKActivator::stop(ctkPluginContext* context)
{
    Q_UNUSED(context)

    delete m_pImpl;
}

实现类2和激活类2

#include "Service.h"
#include <QObject>

class ctkPluginContext;

class WelcomeQtImpl : public QObject, public Service
{
    Q_OBJECT
    Q_INTERFACES(Service)
    /*
    此宏与Q_DECLARE_INTERFACE宏配合使用。
    Q_DECLARE_INTERFACE:声明一个接口类
    Q_INTERFACES:当一个类继承这个接口类,表明需要实现这个接口类
    */

public:
    WelcomeQtImpl(ctkPluginContext* context);
    void welcome() Q_DECL_OVERRIDE;
};

#include "WelcomeQtImpl.h"
#include <QtDebug>

WelcomeQtImpl::WelcomeQtImpl(ctkPluginContext* context)
{

}

void WelcomeQtImpl::welcome()
{
    qDebug() << "Welcome,Qt!";
}

void WelcomeQtActivator::start(ctkPluginContext* context)
{
    ctkDictionary properties;
    properties.insert(ctkPluginConstants::SERVICE_RANKING, 1);
    properties.insert("name", "Qt");

    m_pImpl = new WelcomeQtImpl();
    context->registerService<WelcomeService>(m_pImpl, properties);
}

void WelcomeQtActivator::stop(ctkPluginContext* context)
{
    Q_UNUSED(context)

    delete m_pImpl;
}

获取服务

// 1. 获取所有服务
QList<ctkServiceReference> refs = context->getServiceReferences<Service>();
foreach (ctkServiceReference ref, refs) {
    if (ref) {
        qDebug() << "Name:" << ref.getProperty("name").toString()
                 <<  "Service ranking:" << ref.getProperty(ctkPluginConstants::SERVICE_RANKING).toLongLong()
                  << "Service id:" << ref.getProperty(ctkPluginConstants::SERVICE_ID).toLongLong();
        Service* service = qobject_cast<Service *>(context->getService(ref));
        if (service != Q_NULLPTR)
            service->welcome();
    }
}

// 2. 使用过滤表达式,获取感兴趣的服务
refs = context->getServiceReferences<Service>("(&(name=CTK))");
foreach (ctkServiceReference ref, refs) {
    if (ref) {
        Service* service = qobject_cast<Service *>(context->getService(ref));
        if (service != Q_NULLPTR)
            service->welcome();
    }
}

// 3. 获取某一个服务(由 Service Ranking 和 Service ID 决定)
ctkServiceReference ref = context->getServiceReference<Service>();
if (ref) {
    Service* service = qobject_cast<Service *>(context->getService(ref));
    if (service != Q_NULLPTR)
        service->welcome();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/155086.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决方案|Keithley吉时利源表测试软件的典型应用及案例介绍

数字源表又称源测量单元(SMU)&#xff0c;是数字万用表(DMM)、电压源、实际电流源、电子负载和脉冲发生器的有用功能集成在仪器中&#xff0c;相当于电压源、电流源、电压表、电流表和电阻表的综合体可以作为四象限电压源或电流源提供精确的电压或电流&#xff0c;同时测量电流…

聚类模型(K-means聚类,系统聚类,DBSCAN算法)

所谓的聚类&#xff0c;就是将样本划分为由类似的对象组成的多个类的过程。聚类后&#xff0c;我们可以更加准确的在每个类中单独使用统计模型进行估计、分析或预测&#xff1b;也可以探究不同类之间的相关性和主要差异。聚类和分类的区别&#xff1a;分类是已知类别的&#xf…

Kafka 生产者

Kafka 生产者 生产者就是负责向 Kafka 发送消息的。 生产者业务逻辑 &#xff08;生产者业务逻辑流程&#xff09; 生产者开发示例 一个正常的生产逻辑流程如下&#xff1a; 配置生产者客户端参数及创建相应的生产者实例 构建待发送的消息 发送消息 关闭生产者实例 生…

CSS权威指南(八)基本元素框

文章目录1.基本元素框2.内边距3.边框4.轮廓5.外边距1.基本元素框 文档中每个元素都会生成一个矩形框&#xff0c;我们称之为元素框。这个框体描述元素在文档布局中所占的空间。因此&#xff0c;元素框之间是有影响的&#xff0c;涉及位置和尺寸。 &#xff08;1&#xff09;宽…

如何在 Excel VBA 中插入行

在本文中,我将解释如何使用VBA(Visual Basic for Applications)在Excel中插入行。VBA 是一种编程语言,适用于在Excel和其他Office程序中工作的人员,因此可以通过编写所谓的宏来自动化Excel中的任务。使用VBA编码,我们可以执行Excel中执行的所有大多数任务,就像复制、粘贴…

【手写 Vue2.x 源码】第十六篇 - 生成 render 函数 - 代码拼接

一&#xff0c;前言 上篇&#xff0c;生成 ast 语法树 - 构造树形结构部分 基于 html 特点&#xff0c;使用栈型数据结构记录父子关系开始标签&#xff0c;结束标签及文本的处理方式代码重构及ast 语法树构建过程分析 本篇&#xff0c;使用 ast 语法树生成 render 函数 - 代…

双软认证-深圳市

双软认证是软件企业的认证和软件产品的登记&#xff0c;企业申请双软认证除了获得软件企业和软件产品的认证资质&#xff0c;同时也是对企业知识产权的一种保护方式&#xff0c;更可以让企业享受国家提供给软件行业的税收优惠政策。 想要在这个残酷的市场中生存下去的话&#x…

cc1200 Sub-1 GHz RF Transceivers 开发

一些应用需要定制开发无线串口、指定发送频点、调制方式、加密传输等等&#xff0c;需要使用无线数据的传输场景&#xff0c;需要使用公用频段进行数据传输。一些场景需要使用Sub-1 GHz频点进行数据传输&#xff0c;比如无线串口&#xff0c;其他无线申请&#xff0c;在国内选择…

集群调度情况

1 集群调度 2 调度简介 Scheduler是kubernetes的调度器&#xff0c;主要任务是把定义的pod分配到集群的节点上。听起来非常简单&#xff0c;但有很多要考虑的问题 公平&#xff1a; 如何保证每个节点都能被分配资源 资源高效利用&#xff1a;集群所有资源最大化被使用 效率&…

【 uniapp - 黑马优购 | 购物车页面(1)】如何创建购物车编译模式、 商品列表区域实现

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;讨厌编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc…

LeetCode[692]前K个高频单词

难度&#xff1a;中等题目&#xff1a;给定一个单词列表 words和一个整数 k&#xff0c;返回前 k个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率&#xff0c; 按字典顺序 排序。示例 1&#xff1a;输入: words ["i"…

【异常】记一次因scripts编写错误导致无法正常build的问题

一、npm 与 scripts之间的关系 Node 开发离不开 npm&#xff0c;而脚本功能是 npm 最强大、最常用的功能之一。 npm 允许在package.json文件里面&#xff0c;使用scripts字段定义脚本命令。 比如以下&#xff1a; "scripts": {"dev": "vue-cli-se…

【C++】引用详解

作者&#xff1a;阿润菜菜 专栏&#xff1a;C &#x1f3c3;&#x1f3c3;&#x1f3c3;&#x1f3c3;&#x1f3c3;&#x1f3c3; 本文目录 概念及用法 特性 使用场景 1.做参数 2. 做返回值 从函数栈帧角度理解引用 传值、传引用效率比较 引用和指针的区别 概念及用法 引…

洛谷 P1194 买礼物 (图论 最小生成树)

鸽了好几天了今天写个洛谷的题解 题目描述 又到了一年一度的明明生日了&#xff0c;明明想要买 BB 样东西&#xff0c;巧的是&#xff0c;这 BB 样东西价格都是 AA 元。 但是&#xff0c;商店老板说最近有促销活动&#xff0c;也就是&#xff1a; 如果你买了第 II 样东西&#…

Python OpenCV 数字验证码 字母验证码 图片验证码 自动识别方案 第三方库 识别成功率较高 通用解决方案

前言 在学习的前期可使用现有封装好的轮子试试效果,实际调试能否满足需求。使用已经造好的轮子的好处就是能快速解决当下的问题。若能就继续使用,若不能就接入下一步的深度学习模型训练,其实再验证码识别业务场景大多是情况下用于自动化测试仅针对公司内某一单一的业务线,而…

既然有MySQL了,为什么还要有MongoDB?

目录一、基本概念走起二、MongoDB的主要特征三、MongoDB优缺点&#xff0c;扬长避短1、优点2、缺点四、何时选择MongoDB&#xff1f;为啥要用它&#xff1f;1、MongoDB事务2、多引擎支持各种强大的索引需求3、具体的应用场景4、以下是几个实际的应用案例&#xff1a;5、选择Mon…

gcc后续——链接时的静态库和动态库

本篇文章是链接阶段静动态库的理解&#xff0c;点击查看gcc四个阶段 文章目录1 . 库检测linux所用库查找库的位置2. 动静态库的感性理解1. 动态库的理解2. 静态库的理解3. 静动态库整体理解1. 静态库和静态链接2. 动态库和动态链接3. 静动态库对比1.查询当前linux所用库2. 查看…

【洛谷】P1966 [NOIP2013 提高组] 火柴排队

其实这题本身并不难&#xff0c;考的知识点就是归并排序和逆序对&#xff1b;那么难点在哪呢&#xff1f;就在如何发现这题是个逆序对&#xff1a;至少读到这里我们可以知道&#xff0c;虽然火柴高度是唯一的&#xff0c;但我们不可能直接开一个 max long int 大小的数组&#…

数据库分片

文章目录一、为什么要分片二、什么是数据分片1、垂直分片2、水平分片三、常用分片策略1、Range2、Hash四、相关中间件1、Sharding-Sphere2、Sharding-jdbc一、为什么要分片 从性能方面来说&#xff0c;由于关系型数据库大多采用B树类型的索引&#xff0c;在数据量超过阈(yu)值…

【python】re解析和re模块

目录 正则 RE概念 常见的元字符 量词 贪婪&惰性 修饰符 re模块 findall finditer search match 预加载正则式 内容提取 正则 RE概念 常见的元字符 量词 贪婪&惰性 贪婪匹配.* 惰性匹配.*? 修饰符 修饰符描述re.I使匹配对大小写不敏感re.L做本地化识别&…