Dotnet9

联系请关注微信公众号:Dotnet9

文章

CTK完整教程(OSGI for C++ 实现 C++ Qt 模块化)

来唧唧歪歪 2022-03-24 00:32:33 Qt 26
Qt模块化开发框架介绍

CTK框架实际应用比较可靠,但网上资料很少。本教程围绕 CTK Plugin Framework,探索 C++ 中的模块化技术,并能够基于 CTK 快速搭建 C++ 组件化框架,避免后来的人走弯路。本教程的源码下载地址:项目源代码

CTK介绍

CTK 为支持生物医学图像计算的公共开发包,其全称为 Common Toolkit。CTK插件框架的设计有很大的灵感来自OSGi并且使得应用程序由许多不同的组件组合成一个可扩展模型。这个模型允许通过那些组件间共享对象的服务通信。

当前,CTK 工作的主要范围包括:

  • DICOM:提供了从 PACS 和本地数据库中查询和检索的高级类。包含 Qt 部件,可以轻松地设置服务器连接,并发送查询和查看结果。

  • DICOM Application Hosting:目标是创建 DICOM Part 19 Application Hosting specifications 的 C++ 参考实现。它提供了用于创建主机和托管应用程序的基础设。

  • Widgets:用于生物医学成像应用的 Qt Widgets 集合。

  • Plugin Framework:用于 C++ 的动态组件系统,以 OSGi 规范为模型。它支持一个开发模型,在这个模型中,应用程序(动态地)由许多不同(可重用的)组件组成,遵循面向服务的方法。

  • Command Line Interfaces:一种允许将算法编写为自包含可执行程序的技术,可以在多个终端用户应用程序环境中使用,而无需修改。

CTK Plugin Framework

CTK Plugin Framework 架构策略

  • Qt Creator 的可扩展性。

Qt Creator 通过一种简单、优雅的方式来实现可扩展性,它使用一个通用的 QObject 池来实现某些可用的接口。同时,通过使用嵌入式文本文件(.pluginspec 文件)来向插件添加元数据(例如:Name、Version 等)。

其实严格来说,CTK Plugin Framework 同时借鉴了 OSGi 和 Qt Creator 的思想。

  • Qt 提供了 Qt Plugin System 和 Qt Service Framework。

Qt Plugin System 提供了两套用于创建插件的 API,高级 API 用于扩展 Qt 本身(例如:自定义数据库驱动、图像格式、文本编解码、自定义样式等),低级 API 用于扩展 Qt 应用程序。 对于 Qt Service Framework 来说,它能使服务的开发和访问方式变得更加容易。Qt 服务提供者可以与特定于平台的服务进行交互,而无需向客户端公开平台的细节。每个服务都通过 QObject 指针公开,这意味着客户端可以通过 Qt MetaObject 系统与服务对象进行交互。

  • CTK Plugin Framework 的架构策略是什么?

CTK Plugin Framework 是基于 Qt Plugin System 和 Qt Service Framework 实现的,并且它还添加了以下特性来增强这两个系统:

  • 插件元数据(由 MANIFEST.MF 文件提供);
  • 一个定义良好的插件生命周期和上下文;
  • 综合服务发现和注册;
  • ……

注意: 在 Qt Plugin System 中,插件的元数据由 JSON 文件提供。

CTK Plugin Framework 的核心架构主要包含两个组件:Plugin System 本身和 Service Registry。然而,这两个组件是相互关联的,它们在 API 级别上的组合使得系统更加全面、灵活。

  • Plugin System

CTK Core 依赖于 QtCore 模块,因此 CTK Plugin Framework 基于 Qt Plugin System。Qt API 允许在运行时加载和卸载插件,这个功能在 CTK Plugin Framework 中得到了加强,以支持透明化延迟加载和解决依赖关系。

插件的元数据被编译进插件内部,可以通过 API 进行提取。此外,插件系统还使用 SQLite 缓存了元数据,以避免应用程序加载时间问题。另外,Plugin System 支持通过中央注册中心使用服务。

  • Service Registry

Qt Service Framework 是 Qt Mobility 项目发布的一个 Qt 解决方案,这种服务框架允许“声明式服务”(Getting Started with OSGi: Introducing Declarative Services )和按需加载服务实现。为了启用动态(非持久性)服务,Qt Mobility 服务框架可以与 Service Registry 一起使用,类似于 OSGi Core Specifications 中描述的一样。

CTK Plugin Framework 优点

由于 CTK Plugin Framework 基于 OSGi,因此它继承了一种非常成熟且完全设计的组件系统,这在 Java 中用于构建高度复杂的应用程序,它将这些好处带给了本地(基于 Qt 的)C++ 应用程序。以下内容摘自 Benefits of Using OSGi,并适应于 CTK Plugin Framework:

  • 降低复杂性

使用 CTK Plugin Framework 开发意味着开发插件,它们隐藏了内部实现,并通过定义良好的服务来和其它插件通信。隐藏内部机制意味着以后可以自由地更改实现,这不仅有助于 Bug 数量的减少,还使得插件的开发变得更加简单,因为只需要实现已经定义好的一定数量的功能接口即可。

  • 复用

标准化的组件模型,使得在应用程序中使用第三方组件变得非常容易。

  • 现实情况

CTK Plugin Framework 是一个动态框架,它可以动态地更新插件和服务。在现实世界中,有很多场景都和动态服务模型相匹配。因此,应用程序可以在其所属的领域中重用 Service Registry 的强大基元(注册、获取、用富有表现力的过滤语言列表、等待服务的出现和消失)。这不仅节省了编写代码,还提供了全局可见性、调试工具以及比为专用解决方案实现的更多的功能。在这样的动态环境下编写代码听起来似乎是个噩梦,但幸运的是,有支持类和框架可以消除大部分(如果不是全部的话)痛苦。

  • 开发简单

CTK Plugin Framework 不仅仅是组件的标准,它还指定了如何安装和管理组件。这个 API 可以被插件用来提供一个管理代理,这个管理代理可以非常简单,如命令 shell、图形桌面应用程序、Amazon EC2 的云计算接口、或 IBM Tivoli 管理系统。标准化的管理 API 使得在现有和未来的系统中集成 CTK Plugin Framework 变得非常容易。

  • 动态更新

OSGi 组件模型是一个动态模型,插件可以在不关闭整个系统的情况下被安装、启动、停止、更新和卸载。

  • 自适应

OSGi 组件模型是从头设计的,以允许组件的混合和匹配。这就要求必须指定组件的依赖关系,并且需要组件在其可选依赖性并不总是可用的环境中生存。Service Registry 是一个动态注册表,其中插件可以注册、获取和监听服务。这种动态服务模型允许插件找出系统中可用的功能,并调整它们所能提供的功能。这使得代码更加灵活,并且能够更好地适应变化。

  • 透明性

插件和服务是 CTK 插件环境中的一等公民。管理 API 提供了对插件的内部状态的访问,以及插件之间的连接方式。可以停止部分应用程序来调试某个问题,或者可以引入诊断插件。

  • 版本控制

在 CTK Plugin Framework 中,所有的插件都经过严格的版本控制,只有能够协作的插件才会被连接在一起。

  • 简单

CTK 插件相关的 API 非常简单,核心 API 不到 25 个类。这个核心 API 足以编写插件、安装、启动、停止、更新和卸载它们,并且还包含了所有的监听类。

  • 懒加载

懒加载是软件中一个很好的点,OSGi 技术有很多的机制来保证只有当类真正需要的时候才开始加载它们。例如,插件可以用饿汉式启动,但是也可以被配置为仅当其它插件使用它们时才启动。服务可以被注册,但只有在使用时才创建。这些懒加载场景,可以节省大量的运行时成本。

  • 非独占性

CTK Plugin Framework 不会接管整个应用程序,你可以选择性地将所提供的功能暴露给应用程序的某些部分,或者甚至可以在同一个进程中运行该框架的多个实例。

  • 非侵入

在一个 CTK 插件环境中,不同插件均有自己的环境。它们可以使用任何设施,框架对此并无限制。CTK 服务没有特殊的接口需求,每个 QObject 都可以作为一个服务,每个类(也包括非 QObject)都可以作为一个接口。

CTK编译

使用cmake编译出与系统版本相应的动态库。参见CTK编译教程(64位环境 Windows + Qt + MinGW或MSVC + CMake)

使用 CTKWidgets

新项目-Application(Qt)-Qt Console Application,项目名称为UseCTKWidgets,pro文件的代码:

QT += core gui widgets

TARGET = UseCTKWidgets
TEMPLATE = app

# CTK 安装路径
CTK_INSTALL_PATH = $$PWD/../../CTKInstall

# CTK 相关库所在路径(例如:CTKCore.lib、CTKWidgets.lib)
CTK_LIB_PATH = $$CTK_INSTALL_PATH/lib/ctk-0.1

# CTK 相关头文件所在路径(例如:ctkPluginFramework.h)
CTK_INCLUDE_PATH = $$CTK_INSTALL_PATH/include/ctk-0.1

# 相关库文件(CTKCore.lib、CTKWidgets.lib)
LIBS += -L$$CTK_LIB_PATH -lCTKCore -lCTKWidgets

INCLUDEPATH += $$CTK_INCLUDE_PATH

主函数main加载部件,代码如下:main.cpp

#include <QApplication>
#include <QFormLayout>
#include <QVBoxLayout>

#include <ctkCheckablePushButton.h>
#include <ctkCollapsibleButton.h>
#include <ctkColorPickerButton.h>
#include <ctkRangeWidget.h>

int main(int argc, char* argv[])
{
  QApplication app(argc, argv);

  // 可折叠按钮
  ctkCollapsibleButton* buttons = new ctkCollapsibleButton("Buttons");

  // 可勾选按钮
  ctkCheckablePushButton* checkablePushButton = new ctkCheckablePushButton();
  checkablePushButton->setText("Checkable");

  // 颜色拾取器
  ctkColorPickerButton* colorPickerButton = new ctkColorPickerButton();
  colorPickerButton->setColor(QColor("#9e1414"));

  ctkCollapsibleButton* sliders = new ctkCollapsibleButton("Sliders");

  QFormLayout* buttonsLayout = new QFormLayout;
  buttonsLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
  buttonsLayout->addRow("ctkCheckablePushButton", checkablePushButton);
  buttonsLayout->addRow("ctkColorPickerButton", colorPickerButton);
  buttons->setLayout(buttonsLayout);

  QVBoxLayout* topLevelLayout = new QVBoxLayout();
  topLevelLayout->addWidget(buttons);
  topLevelLayout->addWidget(sliders);

  QFormLayout* slidersLayout = new QFormLayout;
  ctkRangeWidget* rangeWidget = new ctkRangeWidget();
  slidersLayout->addRow("ctkRangeWidget", rangeWidget);
  sliders->setLayout(slidersLayout);

  QWidget topLevel;
  topLevel.setLayout(topLevelLayout);
  topLevel.show();

  return app.exec();
}

项目代码:UseCTKWidgets

初步使用 CTK Plugin Framework

项目结构

由于每一个插件都要建一个子项目,本项目刚开始创建时在QtCreator中选择新建-其他项目-子目录项目,新建项目名称为SampleCTK,然后建立主程序入口项目,这里建立一个控制台项目,取名叫App。

更改项目输出路径:app.pro

DESTDIR = $$OUT_PWD/../bin

主函数中加载插件,启动框架:main.cpp

#include <QCoreApplication>
#include "ctkPluginFrameworkFactory.h"
#include "ctkPluginFramework.h"
#include "ctkPluginException.h"
#include <QDebug>
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    app.setApplicationName("SampleCTK");//给框架创建名称,Linux下没有会报错
    ctkPluginFrameworkFactory frameWorkFactory;
    QSharedPointer<ctkPluginFramework> framework = frameWorkFactory.getFramework();
    try {
        // 初始化并启动插件框架
        framework->init();
        framework->start();
        qDebug() << "CTK Plugin Framework start ...";
    } catch (const ctkPluginException &e) {
        qDebug() << "Failed to initialize the plugin framework: " << e.what();
        qDebug() << e.message() << e.getType();
    }
    return app.exec();
}

如果想把CTK初始化、插件安装启动、获取等操作封装成一个类,那么要注意:需要把CTK相关的变量定义成类属性,不能是局部变量,否则会出现各种问题如获取不了服务、服务引用为空等。

没有报错的话及表示插件加载成功!

其中QSharedPointer framework这个对象比较有意思,既可以作为对象也可以作为对象指针,但要作为插件框架使用必须要用指针方法调用,所以代码里使用“->”。

项目加载CTK框架插件

项目SampleCTK新建文本文件CTK,然后更改扩展名为pri。文件加载CTK安装目录及源代码目录,编译出的动态库就可以当普通动态库使用加载了,CTK.pri里面加载代码为:

# CTK 安装路径
CTK_INSTALL_PATH = $$PWD/../../CTKInstall

# CTK 插件相关库所在路径(例如:CTKCore.lib、CTKPluginFramework.lib)
CTK_LIB_PATH = $$CTK_INSTALL_PATH/lib/ctk-0.1

# CTK 插件相关头文件所在路径(例如:ctkPluginFramework.h)
CTK_INCLUDE_PATH = $$CTK_INSTALL_PATH/include/ctk-0.1

# CTK 插件相关头文件所在路径(主要因为用到了 service 相关东西)
CTK_INCLUDE_FRAMEWORK_PATH = $$PWD/../../CTK-master/Libs/PluginFramework

LIBS += -L$$CTK_LIB_PATH -lCTKCore -lCTKPluginFramework

INCLUDEPATH += $$CTK_INCLUDE_PATH \
               $$CTK_INCLUDE_FRAMEWORK_PATH

将CTK.pri文件的内容引入 pro 文件:app.pro

include($$PWD/../CTK.pri)

CTK插件的接口处理

CTK框架由一个一个可分离的插件组成,框架对插件识别有一定要求,目前网上很多一整块扔出来对新人不太友好,博主这里讲解是尽量拆。单个插件最基本的格式要求分成Activator,qrc文件,以及MANIFEST.MF,以say Hello模块HelloCTK为例。

Activator注册器

每个插件都有自己的注册器Activator。

右键项目选择新建子项目-其他项目-Empty qmake Project,项目名称为HelloCTK,pro文件中添加代码:

QT += core
QT -= gui

TEMPLATE = lib
CONFIG += plugin
TARGET = HelloCTK
DESTDIR = $$OUT_PWD/../bin/plugins

include($$PWD/../CTK.pri)

生成的插件名(TARGET)不要有下划线,因为CTK会默认将插件名中的下划线替换成点号,最后后就导致找不到插件。

项目中添加C++类HelloActivator,代码如下:

hello_activator.h

#ifndef HELLO_ACTIVATOR_H
#define HELLO_ACTIVATOR_H

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

class HelloActivator : public QObject, public ctkPluginActivator
{
    Q_OBJECT
    Q_INTERFACES(ctkPluginActivator)
    Q_PLUGIN_METADATA(IID "HELLO_CTK")
    //向Qt的插件框架声明,希望将xxx插件放入到框架中。

public:
    void start(ctkPluginContext* context);
    void stop(ctkPluginContext* context);

};

#endif // HELLO_ACTIVATOR_H

hello_activator.cpp

#include "hello_activator.h"
#include <QDebug>

void HelloActivator::start(ctkPluginContext* context)
{
    qDebug() << "HelloCTK start";
}

void HelloActivator::stop(ctkPluginContext* context)
{
    qDebug() << "HelloCTK stop";
    Q_UNUSED(context)
    //Q_UNUSED,如果一个函数的有些参数没有用到、某些变量只声明不使用,但是又不想编译器、编辑器报警报,其他没有什么实际性作用
}

activator是标准的Qt插件类,它实现ctkPluginActivator的start、stop函数并对外提供接口。我这里是Qt5的版本,所以使用Q_PLUGIN_METADATA申明插件,Qt4需要用自己的方法实现插件。

qrc文件

创建插件的资源文件,格式如下:

<RCC>
 <qresource prefix="/HelloCTK/META-INF">
   <file>MANIFEST.MF</file>
    </qresource>
</RCC>

插件加载后会寻找同名前缀/META-INF,所以前缀格式固定,将MANIFEST.MF文件添加进来

MENIFEST.MF文件内容如下:

可直接在MF文件里添加自己特有的元数据

Plugin-SymbolicName:HelloCTK
Plugin-Version:1.0.0
Plugin-Number:100 #元数据

注意:Plugin-SymbolicName要满足这里的前缀是:TARGET/META-INF格式。TARGET的名字最好和工程名一致,不然可能出现device not open错误。

文件包含ctk插件的基本信息,只要ctk框架正常识别到文件中Plugin-SymbolicName等信息,则判定它是一个ctk插件,能够正常调用activator中的start、stop函数。这个文件需要拷到插件生成路径下,pro文件中添加代码:

file.path = $$DESTDIR
file.files = MANIFEST.MF
INSTALLS += file

CTK插件启用

根据以上步骤,一个CTK插件接口定义基本完成,我们在App项目下调用观察插件是否能够正常加载。main函数中框架启动成功后添加以下代码:

QString dir = QCoreApplication::applicationDirPath();
    dir += "/plugins/HelloCTK.dll";
    qDebug() << dir;
    QUrl url = QUrl::fromLocalFile(dir);
    QSharedPointer<ctkPlugin> plugin;
    try
    {
        plugin = framework->getPluginContext()->installPlugin(url);
    }catch(ctkPluginException e){
        qDebug() << e.message() << e.getType();
    }
    try{
        plugin->start(ctkPlugin::START_TRANSIENT);
    }catch(ctkPluginException e){
        qDebug() << e.message() << e.getType();
    }

控制台打印输出:

"C:/d/mmm/qt/ctk/CTK-examples/build-SampleCTK-Desktop_Qt_5_15_1_MSVC2019_64bit-Release/bin/plugins"
 HelloCTK start

成功调用HelloCTK中start内打印输出,则表明ctk插件接口正常定义并能成功加载。其中start(ctkPlugin::START_TRANSIENT)表示立即启用插件,不设置参数的话加载后也不会立即打印输出。

基本使用 CTK Plugin Framework

CTK插件间通信

CTK框架插件化开发实现功能的隔离,插件通信需要参照固定标准,这里介绍两种插件间通信的方法。

通信方法一. 注册接口调用

注册接口调用
函数接口

接口就是纯虚函数类,也就是最终的服务的前身。

上面我们已经编译出需要的动态库,首先确定我们需要插件向外部暴露的功能有什么,比如这里我们需要说”Hello,CTK!”的操作,定义头文件如下:hello_service.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

Q_DECLARE_INTERFACE将接口类向Qt系统申明,然后添加它的实现对象:

接口的实现

插件就是实现这个接口类的实现类,所以理论上有多少个实现类就有多少个插件。

hello_impl.h

#ifndef HELLO_IMPL_H
#define HELLO_IMPL_H

#include "hello_service.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

hello_impl.cpp

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

HelloImpl::HelloImpl(ctkPluginContext* context)
{
    context->registerService<HelloService>(this);
}

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

这仍是Qt的插件定义格式,但是不会作为插件导出,外部功能接口可以自定义。

服务注册(Activator注册服务)

激活类里有一个独占智能指针,指向接口类【使用多态,指针都指向父类】,然后在start里new一个实现类,注册这个实现类为服务,功能是实现接口类的接口,然后将智能指针指向这个实现类。可以理解为以后向框架索取这个服务的时候,实际获取的就是这个new出来的实现类。如果不用智能指针,就需要在stop里手动delete这个实现类。

每个插件都有自己的注册器Activator,功能节接口完成后,在插件启动时注册到ctk框架的服务中,代码如下:hello_activator.cpp

#include "hello_activator.h"
#include "hello_impl.h"

void HelloActivator::start(ctkPluginContext* context)
{
    s.reset(new HelloImpl(context));
    //调用注册服务context->registerService<HelloService>(this);
}
接口调用

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

主函数框架及插件加载完成后,即可调用插件接口,代码如下:main.cpp

#include "../HelloCTK/hello_service.h"
    
// 获取服务引用
ctkServiceReference reference = context->getServiceReference<HelloService>();
if (reference) {
    // 获取指定 ctkServiceReference 引用的服务对象
    HelloService* service = qobject_cast<HelloService *>(context->getService(reference));
    if (service != Q_NULLPTR) {
        // 调用服务
        service->sayHello();
    }
}

在获取服务的时候,有两个重载方式【可直接使用的】

1、HelloService* service = context->getService<HelloService>(reference);
2、HelloService* service = qobject_cast<HelloService*>(context->getService(reference));

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

项目代码:SampleCTK

优化解耦(实现类和激活类分离)

编写插件主要有3个步骤:接口类、实现类、激活类。不在实现类的构造函数里注册服务,降低耦合性,接口类就只做接口声明,实现类就只实现接口,激活类就负责将服务整合到ctk框架中。

接口类没有什么变化,实现类少了注册的代码,构造函数也无参数,注册的过程放在了激活类里。

  1. 实现类

.h

HelloImpl( );

.cpp

HelloImpl::HelloImpl( )
{
     qDebug()<<"this is imp";
}
  1. 激活类

.cpp

void HelloActivator::start(ctkPluginContext* context)
{
    HelloImpl* helloImpl = new HelloImpl(context);
    context->registerService<HelloService>(helloImpl);
    s.reset(helloImpl);
}
接口、插件、服务的关系

1、1对1

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

上面项目为典型1对1关系。

2、多对1

1个类实现了多个接口类,输出多个服务和1个插件,无论想使用哪个服务最终都通过这同一个插件来实现。

实现类,实现多个接口。

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

GreetImpl::GreetImpl()
{

}

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

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

获取不同服务

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

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

具体实现参见项目:PluginAndService/MultipleInterfaces

3、1对多

1接口由多个个类实现,也就是某一个问题提供了多种解决思路,输出1个服务和多个插件,通过ctkPluginConstantsSERVICE_RANKING和ctkPluginConstantsSERVICE_ID来调用不同的插件。这里虽然有两个插件,但都是被编译到同一个dll中的。服务的获取策略如下:容器会返回排行最低的服务,返回注册时SERVICE_RANKING属性值最小的服务。如果有多个服务的排行值相等,那么容器将返回PID值最小的那个服务。

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

在使用1接口2插件的时候,虽然是两个插件,也会有两个激活类【从原理上来讲1个激活类就行了,但是在start里注册两次】,其中的IID只能有一个。从Qt插件基础上来说,一个dll只能有一个IID。

多个实现类,实现1个接口。

welcome_ctk_impl.cpp

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

WelcomeCTKImpl::WelcomeCTKImpl()
{

}

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

welcome_qt_impl.cpp

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

WelcomeQtImpl::WelcomeQtImpl()
{

}

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

对应的多个激活类

welcome_ctk_activator.cpp

#include "welcome_ctk_impl.h"
#include "welcome_ctk_activator.h"
#include <QtDebug>

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;
}

welcome_qt_activator.cpp

#include "welcome_qt_impl.h"
#include "welcome_qt_activator.h"
#include <QtDebug>

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<WelcomeService>();
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();
        WelcomeService* service = qobject_cast<WelcomeService *>(context->getService(ref));
        if (service != Q_NULLPTR)
            service->welcome();
    }
}

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

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

具体实现参见项目:PluginAndService/OneInterface

通信方法二. 事件监听

CTK框架中的事件监听,即观察者模式流程上是这样:接收者注册监听事件->发送者发送事件->接收者接收到事件并响应;相比调用插件接口,监听事件插件间依赖关系更弱,不用指定事件的接收方和发送方是谁。

要使用CTK框架的事件服务,准备工作应该从cmake开始,编译出支持事件监听的动态库,名称为liborg_commontk_eventadmin.dll。现在要完成的内容是,从上面生成的主窗体中,以事件监听的方式调用一个子窗体。

1、通信主要用到了ctkEventAdmin结构体,主要定义了如下接口:

postEvent:类通信形式异步发送事件

sendEvent:类通信形式同步发送事件

publishSignal:信号与槽通信形式发送事件

unpublishSignal:取消发送事件

subscribeSlot:信号与槽通信形式订阅时间,返回订阅的ID

unsubscribeSlot:取消订阅事件

updateProperties:更新某个订阅ID的主题

2、通信的数据是:ctkDictionary

其实就是个hash表:typedef QHash<QString,QVariant> ctkDictionary

事件监听

具体项目:EventAdmin/SendEvent

加载EventAdmin动态库

添加动态库可以使用ctkPluginFrameworkLauncher,代码如下:main.cpp

// 获取插件所在位置
// 在插件的搜索路径列表中添加一条路径
ctkPluginFrameworkLauncher::addSearchPath("../../../../CTKInstall/lib/ctk-0.1/plugins");
// 设置并启动 CTK 插件框架
ctkPluginFrameworkLauncher::start("org.commontk.eventadmin");
 ……
// 停止插件
ctkPluginFrameworkLauncher::stop();
事件注册监听(接收插件)

首先编写我们需要的接收者模块,并注册监听事件,这里我们新建一个模块BlogEventHandler,模块的接口处理参见上面“CTK插件的接口处理”。插件部分代码如下:

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

与上面自定义接口不同,这里我们实例化ctkEventHandler对象,并实现handleEvent接口。构造函数中注册的服务对象是ctkEventHandler,在注册时指定触发的事件,当事件触发时调用该对象的handleEvent实现指定操作。

事件发送(发送插件)

监听对象完成后调用比较简单,代码如下: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);
    }
}

项目代码:EventAdmin/SendEvent

事件发送方式(类通信、信号槽通信)
1、类通信

原理就是直接将信息使用CTK的eventAdmin接口send/post出去。

上面项目为典型类通信。

2、信号槽通信

原理是将Qt自己的信号与CTK的发送事件绑定、槽与事件订阅绑定。

接收槽

void BlogEventHandlerUsingSlotsActivator::start(ctkPluginContext* context)
{
    m_pEventHandler = new BlogEventHandlerUsingSlots();

    ctkDictionary props;
    props[ctkEventConstants::EVENT_TOPIC] = "org/commontk/bloggenerator/published";
    ctkServiceReference ref = context->getServiceReference<ctkEventAdmin>();
    if (ref) {
        ctkEventAdmin* eventAdmin = context->getService<ctkEventAdmin>(ref);
        eventAdmin->subscribeSlot(m_pEventHandler, SLOT(onBlogPublished(ctkEvent)), props, Qt::DirectConnection);
    }
}

发送信号

BlogManagerUsingSignals::BlogManagerUsingSignals(ctkPluginContext *context)
{
    ctkServiceReference ref = context->getServiceReference<ctkEventAdmin>();
    if (ref) {
        ctkEventAdmin* eventAdmin = context->getService<ctkEventAdmin>(ref);
        // 使用 Qt::DirectConnection 等同于 ctkEventAdmin::sendEvent()
        eventAdmin->publishSignal(this, SIGNAL(blogPublished(ctkDictionary)), "org/commontk/bloggenerator/published", Qt::DirectConnection);
    }
}

具体项目:项目代码:EventAdmin/SignalSlot

二者的区别

1、通过event事件通信,是直接调用CTK的接口,把数据发送到CTK框架;通过信号槽方式,会先在Qt的信号槽机制中转一次,再发送到CTK框架。故效率上来讲,event方式性能高于信号槽方式。

2、两种方式发送数据到CTK框架,这个数据包含:主题+属性。主题就是topic,属性就是ctkDictionary。 一定要注意signal方式的信号定义,参数不能是自定义的,一定要是ctkDictionary,不然会报信号槽参数异常错误。

3、两种方式可以混用,如发送event事件,再通过槽去接收;发送signal事件,再通过event是接收。

4、同步:sendEvent、QtDirectConnection;异步:postEvent、QtQueuedConnection

这里的同步是指:发送事件之后,订阅了这个主题的数据便会处理数据【handleEvent、slot】,处理的过程是在发送者的线程完成的。可以理解为在发送了某个事件之后,会立即执行所有订阅此事件的回调函数。

异步:发送事件之后,发送者便会返回不管,订阅了此事件的所有插件会根据自己的消息循环,轮到了处理事件后才会去处理。不过如果长时间没处理,CTK也有自己的超时机制。如果事件处理程序花费的时间比配置的超时时间长,那么就会被列入黑名单。一旦处理程序被列入黑名单,它就不会再被发送任何事件。

插件依赖

插件加载时一般根据首字母大小自动加载,所以在插件启用时,某个插件还没有被调用,所以发送事件没有接收方,这样就要考虑到插件依赖关系,在MANIFEST.MF中添加依赖:

Plugin-SymbolicName:Plugin-xxx-1
Plugin-Version:1.0.0
Require-Plugin:Plugin-xxx-2; plugin-version="[1.0,2.0)"; resolution:="mandatory"

Plugin-xxx-2:为需要依赖的插件名【就是另一个插件在MANIFEST.MF里的Plugin-SymbolicName】;

[1.0,2.0):为Plugin-xxx-2的版本,这里是左闭右开区间,默认是1.0,;

resolution:有两个选择,optional、mandatory。前者是弱依赖,就算依赖的插件没有,当前插件也能正常使用,后者是强依赖,如果没有依赖的插件,就当前插件就不能被start。

这样就向框架申明了,该插件加载时需要先加载Plugin-xxx-2插件,所有用户插件都应该有这样一份申明。

具体实现参见项目:RequirePlugin

插件元数据

获取MANIFEST.MF中的数据

QHash<QString, QString> headers = plugin->getHeaders();
ctkVersion version = ctkVersion::parseVersion(headers.value(ctkPluginConstants::PLUGIN_VERSION)); 
QString name = headers.value(ctkPluginConstants::PLUGIN_NAME);

具体实现参见项目:GetMetaData

高级使用 CTK Plugin Framework

CTK 服务工厂

注册服务的时候能够用服务工厂来注册,访问服务 getService中的plugin参数是执行ctkPluginContext::getService(const ctkServiceReference&)的插件,从而这里工厂根据执行的不同插件名称返回了不同的服务实现。

服务工厂的作用:

  1. 在服务中可以知道是哪个其他插件在使用它;

  2. 懒汉式使用服务,需要的时候才new;

  3. 其他插件使用有服务工厂和使用无服务工的服务,没有任何区别,代码都一样;

  4. 可根据需要创建多种实现的服务,就是:多种服务对应一个插件。

接口类

#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

多实现类

#ifndef HELLO_IMPL_H
#define HELLO_IMPL_H

#include "hello_service.h"
#include <QObject>
#include <QtDebug>

// HelloWorld
class HelloWorldImpl : public QObject, public HelloService
{
    Q_OBJECT
    Q_INTERFACES(HelloService)

public:
    void sayHello() Q_DECL_OVERRIDE {
        qDebug() << "Hello,World!";
    }
};

// HelloCTK
class HelloCTKImpl : public QObject, public HelloService
{
    Q_OBJECT
    Q_INTERFACES(HelloService)

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

#endif // HELLO_IMPL_H

服务工厂类

#ifndef SERVICE_FACTORY_H
#define SERVICE_FACTORY_H

#include <ctkServiceFactory.h>
#include <ctkPluginConstants.h>
#include <ctkVersion.h>
#include "hello_impl.h"

class ServiceFactory : public QObject, public ctkServiceFactory
{
    Q_OBJECT
    Q_INTERFACES(ctkServiceFactory)

public:
    ServiceFactory() : m_counter(0) {}

    // 创建服务对象
    QObject* getService(QSharedPointer<ctkPlugin> plugin, ctkServiceRegistration registration) Q_DECL_OVERRIDE {
        Q_UNUSED(registration)

        qDebug() << "Create object of HelloService for: " << plugin->getSymbolicName();
        m_counter++;
        qDebug() << "Number of plugins using service: " << m_counter;

        QHash<QString, QString> headers = plugin->getHeaders();
        ctkVersion version = ctkVersion::parseVersion(headers.value(ctkPluginConstants::PLUGIN_VERSION));
        QString name = headers.value(ctkPluginConstants::PLUGIN_NAME);

        QObject* hello = getHello(version);
        return hello;
    }

    // 释放服务对象
    void ungetService(QSharedPointer<ctkPlugin> plugin, ctkServiceRegistration registration, QObject* service) Q_DECL_OVERRIDE {
        Q_UNUSED(plugin)
        Q_UNUSED(registration)
        Q_UNUSED(service)

        qDebug() << "Release object of HelloService for: "  << plugin->getSymbolicName();
        m_counter--;
        qDebug() << "Number of plugins using service: "  << m_counter;
    }

private:
    // 根据不同的版本,获取不同的服务
    QObject* getHello(ctkVersion version) {
        if (version.toString().contains("alpha")) {
            return new HelloWorldImpl();
        } else {
            return new HelloCTKImpl();
        }
    }

private:
    int m_counter;  // 计数器
};

#endif // SERVICE_FACTORY_H

可以根据插件,获取不同的服务。若主框架【main.cpp】的symbolicName是system.plugin

激活类

#ifndef HELLO_ACTIVATOR_H
#define HELLO_ACTIVATOR_H

#include <ctkPluginActivator.h>
#include <ctkPluginContext.h>
#include "hello_service.h"
#include "service_factory.h"

class HelloActivator : public QObject, public ctkPluginActivator
{
    Q_OBJECT
    Q_INTERFACES(ctkPluginActivator)
    Q_PLUGIN_METADATA(IID "HELLO")

public:
    // 注册服务工厂
    void start(ctkPluginContext* context) {
        ServiceFactory *factory = new ServiceFactory();
        context->registerService<HelloService>(factory);
    }

    void stop(ctkPluginContext* context) {
        Q_UNUSED(context)
    }
};

#endif // HELLO_ACTIVATOR_H

插件中访问服务

// 访问服务
ctkServiceReference reference = context->getServiceReference<HelloService>();
if (reference) {
    HelloService* service = qobject_cast<HelloService *>(context->getService(reference));
    if (service != Q_NULLPTR) {
        service->sayHello();
    }
}

具体实现参见项目:ServiceFactory

CTK 事件监听

CTK一共有三种事件可以监听:框架事件、插件事件、服务事件。但是这些事件只有再变化时才能监听到,如果已经变化过后,进入一个稳定的状态,这时才去监听,那么是无法监听到的。

框架事件

针对整个框架的,相当于只有一个,因为框架只有一个,但是这里有个问题,就是监听这个事件是在框架初始化之后的,所以根本没法监听到框架事件的初始化,只能监听到结束的事件。类型有

FRAMEWORK_STARTED
PLUGIN_ERROR
PLUGIN_WARNING
PLUGIN_INFO
FRAMEWORK_STOPPED
FRAMEWORK_STOPPED_UPDATE
FRAMEWORK_WAIT_TIMEDOUT

服务事件

在创建、回收插件时的事情,主要体现在服务的注册和注销。类型有

REGISTERED
MODIFIED
MODIFIED_ENDMATCH
UNREGISTERING

插件事件

在安装、启动插件的过程中呈现的,主要就是插件的一个状态的变化。类型有

INSTALLED
RESOLVED
LAZY_ACTIVATION
STARTING
STARTED
STOPPING
STOPPED
UPDATED
UNRESOLVED
UNINSTALLED

监听例子

监听类,event_listener.h

#ifndef EVENT_LISTENER_H
#define EVENT_LISTENER_H

#include <QObject>
#include <ctkPluginFrameworkEvent.h>
#include <ctkPluginEvent.h>
#include <ctkServiceEvent.h>

class EventListener : public QObject
{
    Q_OBJECT

public:
    explicit EventListener(QObject *parent = Q_NULLPTR);
    ~EventListener();

public slots:
    // 监听框架事件
    void onFrameworkEvent(const ctkPluginFrameworkEvent& event);
    // 监听插件事件
    void onPluginEvent(const ctkPluginEvent& event);
    // 监听服务事件
    void onServiceEvent(const ctkServiceEvent& event);
};

#endif // EVENT_LISTENER_H

event_listener.cpp

#include "event_listener.h"

EventListener::EventListener(QObject *parent)
    : QObject(parent)
{
}

EventListener::~EventListener()
{
}

// 监听框架事件
void EventListener::onFrameworkEvent(const ctkPluginFrameworkEvent& event)
{
    if (!event.isNull()) {
        QSharedPointer<ctkPlugin> plugin = event.getPlugin();
        qDebug() << "FrameworkEvent: [" << plugin->getSymbolicName() << "]" << event.getType() << event.getErrorString();
    } else {
        qDebug() << "The framework event is null";
    }
}

// 监听插件事件
void EventListener::onPluginEvent(const ctkPluginEvent& event)
{
    if (!event.isNull()) {
        QSharedPointer<ctkPlugin> plugin = event.getPlugin();
        qDebug() << "PluginEvent: [" << plugin->getSymbolicName() << "]" << event.getType();
    } else {
        qDebug() << "The plugin event is null";
    }
}

// 监听服务事件
void EventListener::onServiceEvent(const ctkServiceEvent &event)
{
    if (!event.isNull()) {
        ctkServiceReference ref = event.getServiceReference();
        QSharedPointer<ctkPlugin> plugin = ref.getPlugin();
        qDebug() << "ServiceEvent: [" << event.getType() << "]" << plugin->getSymbolicName() << ref.getUsingPlugins();
    } else {
        qDebug() << "The service event is null";
    }
}

启用监听,main.cpp:

// 事件监听
EventListener listener;
context->connectFrameworkListener(&listener, SLOT(onFrameworkEvent(ctkPluginFrameworkEvent)));
context->connectPluginListener(&listener, SLOT(onPluginEvent(ctkPluginEvent)));
// 过滤 ctkEventAdmin 服务
// QString filter = QString("(%1=%2)").arg(ctkPluginConstants::OBJECTCLASS).arg("org.commontk.eventadmin");
context->connectServiceListener(&listener, "onServiceEvent"); //, filter);

具体实现参见项目:EventListener

CTK 服务追踪

服务追踪:如果想在B插件里使用A服务,可以专门写一个类继承ctkServiceTracker,在这个类里完成对A服务的底层操作,然后在B插件里通过这个类提供的接口来使用回收A服务。

理论上ctkServiceTracker和A服务应该是一起的,这里有点像服务工厂。优点就是获取服务的代码简单,不用各种判断空指针。

服务A

服务A实现类,log_impl.cpp

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

LogImpl::LogImpl()
{

}

void LogImpl::debug(QString msg)
{
    qDebug() << "This is a debug message: " << msg;
}

服务A激活类,log_activator.cpp

#include "log_impl.h"
#include "log_activator.h"
#include <ctkPluginContext.h>
#include <QtDebug>

void LogActivator::start(ctkPluginContext* context)
{
    m_pPlugin = new LogImpl();
    context->registerService<LogService>(m_pPlugin);
}

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

    delete m_pPlugin;
    m_pPlugin = Q_NULLPTR;
}

服务A的服务追踪类

追踪类,建立时机:

1、可以在封装A服务的时候就建立,作为一种工具向外提供,但是不应该被编译进插件中,它并不是插件的功能而是访问插件的工具;

2、也可以在B插件中建立,完全和A服务独立开,作为访问A服务的一种手段;

3、单独建立一个空工程,为项目中的所有服务建立对应的追踪类,然后放在同一个文件夹中,其他想要的自己使用就行。

注意:B插件如果想要使用A服务,需要service_tracker.h、service_tracker.cpp、A服务的接口类。

本例采用第二种。

service_tracker.h

#ifndef SERVICE_TRACKER_H
#define SERVICE_TRACKER_H

#include <ctkPluginContext.h>
#include <ctkServiceTracker.h>
#include "../Log/log_service.h"

class ServiceTracker : public ctkServiceTracker<LogService *>
{
public:
    ServiceTracker(ctkPluginContext* context) : ctkServiceTracker<LogService *>(context) {}
    ~ServiceTracker() {}

protected:
    // 在 Service 注册时访问
    LogService* addingService(const ctkServiceReference& reference) Q_DECL_OVERRIDE {
        qDebug() << "Adding service:" << reference.getPlugin()->getSymbolicName();
        // return ctkServiceTracker::addingService(reference);

        LogService* service = (LogService*)(ctkServiceTracker::addingService(reference));
        if (service != Q_NULLPTR) {
            service->debug("Ok");
        }

        return service;
    }

    void modifiedService(const ctkServiceReference& reference, LogService* service) Q_DECL_OVERRIDE {
        qDebug() << "Modified service:" << reference.getPlugin()->getSymbolicName();
        ctkServiceTracker::modifiedService(reference, service);
    }

    void removedService(const ctkServiceReference& reference, LogService* service) Q_DECL_OVERRIDE {
        qDebug() << "Removed service:" << reference.getPlugin()->getSymbolicName();
        ctkServiceTracker::removedService(reference, service);
    }
};

#endif // SERVICE_TRACKER_H

插件B

插件B实现类, login_impl.cpp

#include "login_impl.h"
#include "service_tracker.h"

LoginImpl::LoginImpl(ServiceTracker *tracker)
    : m_pTracker(tracker)
{

}

bool LoginImpl::login(const QString& username, const QString& password)
{ 
    LogService* service = (LogService*)(m_pTracker->getService());

    if (QString::compare(username, "root") == 0 && QString::compare(password, "123456") == 0) {
        if (service != Q_NULLPTR)
            service->debug("Login successfully");
        return true;
    } else {
        if (service != Q_NULLPTR)
            service->debug("Login failed");
        return false;
    }
}

插件B激活类,login_activator.cpp

#include "login_impl.h"
#include "login_activator.h"
#include "service_tracker.h"
#include <ctkPluginContext.h>

void LoginActivator::start(ctkPluginContext* context)
{
    // 开启服务跟踪器
    m_pTracker = new ServiceTracker(context);
    m_pTracker->open();

    m_pPlugin = new LoginImpl(m_pTracker);
    m_registration = context->registerService<LoginService>(m_pPlugin);
}

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

    // 注销服务
    m_registration.unregister();

    // 关闭服务跟踪器
    m_pTracker->close();

    delete m_pPlugin;
    m_pPlugin = Q_NULLPTR;
}

使用插件B

// 获取插件所在位置
QString path = QCoreApplication::applicationDirPath() + "/plugins";

// 遍历路径下的所有插件
QDirIterator itPlugin(path, QStringList() << "*.dll" << "*.so", QDir::Files);
while (itPlugin.hasNext()) {
    QString strPlugin = itPlugin.next();
    try {
        // 安装插件
        QSharedPointer<ctkPlugin> plugin = context->installPlugin(QUrl::fromLocalFile(strPlugin));
        // 启动插件
        plugin->start(ctkPlugin::START_TRANSIENT);
        qDebug() << "Plugin start ...";
    } catch (const ctkPluginException &e) {
        qDebug() << "Failed to install plugin" << e.what();
        return -1;
    }
}

// 获取服务引用
ctkServiceReference reference = context->getServiceReference<LoginService>();
if (reference) {
    // 获取指定 ctkServiceReference 引用的服务对象
    LoginService* service = qobject_cast<LoginService *>(context->getService(reference));
    if (service != Q_NULLPTR) {
        // 调用服务
        service->login("root", "123456");
    }
}

具体参见项目:ServiceTracker

留言

回复