CTK完全チュートリアル (OSGI for C++ 実装 C++ Qt モジュール化)

CTK完全チュートリアル (OSGI for C++ 実装 C++ Qt モジュール化)

Qtモジュール化開発フレームワークの紹介

最終更新 2022/03/24 0:32
来唧唧歪歪
読了目安 24 分
カテゴリ
フロントエンド
タグ
プラグイン化 モジュール化 CTK OSGI 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 仕様の C++ 参照実装を作成することを目的としています。ホストおよびホストアプリケーションを作成するための基盤を提供します。

  • Widgets:生物医学イメージングアプリケーション向けの Qt ウィジェットのコレクション。

  • Plugin Framework:OSGi 仕様をモデルとした C++ 向けの動的コンポーネントシステム。多くの異なる(再利用可能な)コンポーネントからアプリケーションが(動的に)構成され、サービス指向のアプローチに従う開発モデルをサポートします。

  • Command Line Interfaces:アルゴリズムを自己完結型の実行可能プログラムとして記述し、修正なしで複数のエンドユーザーアプリケーション環境で使用できるようにする技術。

CTK Plugin Framework

CTK Plugin Framework のアーキテクチャ戦略

  • Qt Creator の拡張性

Qt Creator は、共通の QObject プールを使用していくつかの利用可能なインターフェースを実装することで、シンプルでエレガントな方法で拡張性を実現しています。同時に、埋め込みテキストファイル(.pluginspec ファイル)を使用して、プラグインにメタデータ(名前、バージョンなど)を追加します。

厳密に言えば、CTK Plugin Framework は OSGi と Qt Creator の両方のアイデアを借用しています。

  • Qt は Qt Plugin System と Qt Service Framework を提供

Qt Plugin System は、プラグインを作成するための 2 セットの API を提供します。高レベル API は Qt 自体を拡張するためのもの(カスタムデータベースドライバー、画像形式、テキストコーデック、カスタムスタイルなど)であり、低レベル API は Qt アプリケーションを拡張するためのものです。

Qt Service Framework は、サービスの開発とアクセスを容易にします。Qt サービスプロバイダーは、プラットフォームの詳細をクライアントに公開することなく、プラットフォーム固有のサービスと対話できます。各サービスは QObject ポインタを介して公開されるため、クライアントは Qt MetaObject システムを介してサービスオブジェクトと対話できます。

  • CTK Plugin Framework のアーキテクチャ戦略とは?

CTK Plugin Framework は Qt Plugin System と Qt Service Framework に基づいて実装され、さらにこれらの 2 つのシステムを強化するために以下の機能を追加しています:

  • プラグインメタデータ(MANIFEST.MF ファイルで提供)
  • 明確に定義されたプラグインのライフサイクルとコンテキスト
  • 総合的なサービス検出と登録
  • ...

注意:Qt Plugin System では、プラグインのメタデータは JSON ファイルで提供されます。

CTK Plugin Framework のコアアーキテクチャは、主に 2 つのコンポーネントで構成されています:Plugin System 自体と Service Registry です。ただし、これら 2 つのコンポーネントは相互に関連しており、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 サービスフレームワークは、OSGi Core Specifications で説明されているものと同様に、Service Registry と共に使用できます。

CTK Plugin Framework の利点

CTK Plugin Framework は OSGi に基づいているため、Java で高度に複雑なアプリケーションを構築するために使用される、非常に成熟した完全に設計されたコンポーネントシステムを継承しており、これらの利点をネイティブ(Qt ベースの)C++ アプリケーションにもたらします。以下は Benefits of Using OSGi からの抜粋で、CTK Plugin Framework に適用されています:

  • 複雑性の低減

CTK Plugin Framework を使用した開発は、内部実装を隠蔽し、明確に定義されたサービスを介して他のプラグインと通信するプラグインの開発を意味します。内部メカニズムを隠蔽することで、後で実装を自由に変更でき、バグの削減に役立つだけでなく、定義済みの機能インターフェースを実装するだけでよいため、プラグインの開発がより簡単になります。

  • 再利用

標準化されたコンポーネントモデルにより、サードパーティのコンポーネントをアプリケーションで使用することが非常に容易になります。

  • 現実への適合

CTK Plugin Framework は動的フレームワークであり、プラグインとサービスを動的に更新できます。現実世界では、動的サービスモデルに適合する多くのシナリオがあります。そのため、アプリケーションはその分野で Service Registry の強力なプリミティブ(登録、取得、表現力豊かなフィルター言語でのリスト、サービスの出現と消失の待機)を再利用できます。これにより、コード作成の節約だけでなく、グローバルな可視性、デバッグツール、専用ソリューションで実装されるよりも多くの機能が提供されます。このような動的環境でコードを書くことは悪夢のように思えるかもしれませんが、幸いなことに、ほとんどの(すべてではないにしても)苦痛を取り除くサポートクラスとフレームワークがあります。

  • シンプルな開発

CTK Plugin Framework はコンポーネントの標準化だけでなく、コンポーネントのインストールと管理方法も指定しています。この API は、プラグインが管理エージェントを提供するために使用できます。この管理エージェントは、コマンドシェル、グラフィカルデスクトップアプリケーション、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 ファイルを追加します。

MANIFEST.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 フレームワークのプラグイン化開発では、機能の分離を実現します。プラグイン間の通信は固定の標準に従う必要があります。ここでは 2 つのプラグイン間通信方法を紹介します。

通信方法 1. インターフェースを登録して呼び出す

インターフェースの登録と呼び出し
関数インターフェース

インターフェースは純粋仮想関数クラスであり、最終的なサービスの前身です。

上記で必要な動的ライブラリをコンパイルしました。まず、プラグインが外部に公開する機能を決定する必要があります。例えば、ここでは "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 内で実装クラスを手動で削除する必要があります。

各プラグインには独自のレジストラ 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();
    }
}

サービスを取得する際、2 つのオーバーロード方法があります【直接使用可能】:

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

サービスとはインターフェースのインスタンスであり、サービスが生成されるたびにレジストラの start が 1 回呼び出されます。インターフェースをクラスと見なし、サービスはクラスから 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 を使用して異なるプラグインを呼び出します。ここでは 2 つのプラグインがありますが、両方とも同じ dll にコンパイルされています。サービスの取得戦略は次のとおりです:コンテナはランキングが最も低いサービスを返します。つまり、登録時に SERVICE_RANKING 属性値が最も小さいサービスを返します。複数のサービスのランキング値が等しい場合、コンテナは PID 値が最も小さいサービスを返します。

あるプラグインが別のプラグインを呼び出すたびに、1 つのインスタンスだけが生成され、メモリに保存されます。複数回呼び出しても複数のサービスインスタンスは生成されません。

1 つのインターフェースに 2 つのプラグインを使用する場合、2 つのプラグインがあるため、2 つのアクティベータクラスもあります(原理的には 1 つのアクティベータクラスで十分ですが、start 内で 2 回登録します)。IID は 1 つだけです。Qt プラグインベースでは、1 つの dll に 1 つの 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 を参照してください。

通信方法 2. イベントリスニング

CTK フレームワークのイベントリスニング、すなわちオブザーバーパターンのフローは次のとおりです:受信側がリスニングイベントを登録 → 送信側がイベントを送信 → 受信側がイベントを受信して応答。プラグインインターフェースを呼び出す方法と比較して、イベントリスニングはプラグイン間の依存関係が弱く、イベントの受信側と送信側を指定する必要がありません。

CTK フレームワークのイベントサービスを使用するには、準備作業は cmake から始まり、イベントリスニングをサポートする動的ライブラリをコンパイルします。名前は liborg_commontk_eventadmin.dll です。ここで行う内容は、上記で生成したメインフォームから、イベントリスニング方式でサブフォームを呼び出すことです。

  1. 通信には主に ctkEventAdmin 構造体を使用し、主に次のインタフェースを定義します:

postEvent:クラス通信形式で非同期にイベントを送信

sendEvent:クラス通信形式で同期にイベントを送信

publishSignal:シグナルとスロット通信形式でイベントを送信

unpublishSignal:イベント送信をキャンセル

subscribeSlot:シグナルとスロット通信形式でイベントを購読し、購読 ID を返す

unsubscribeSlot:イベント購読をキャンセル

updateProperties:特定の購読 ID のトピックを更新

  1. 通信データは:ctkDictionary

これは実質的にはハッシュテーブルです: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:2 つの選択肢があります。optional と mandatory。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. 必要に応じて複数の実装のサービスを作成できます。つまり、複数のサービスが 1 つのプラグインに対応します。

インターフェースクラス

#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 にはリッスン可能な 3 種類のイベントがあります:フレームワークイベント、プラグインイベント、サービスイベント。ただし、これらのイベントは変化時にのみリッスンできます。変化が完了して安定状態になった後にリッスンしようとしても、リッスンできません。

フレームワークイベント

フレームワーク全体に対するもので、実質的に 1 つだけです(フレームワークは 1 つだけ)。ただし、このイベントをリッスンするのはフレームワーク初期化後であるため、フレームワークイベントの初期化をリッスンすることはできず、終了イベントのみリッスンできます。タイプは以下の通り:

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 サービスは一緒にあるべきです。ここではサービスファクトリに似ています。利点は、サービス取得のコードがシンプルになり、さまざまな null ポインタチェックが不要になることです。

サービス 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 サービスのインターフェースクラスが必要です。

この例では 2 番目の方法を採用します。

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:
    // サービス登録時にアクセス
    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 を参照してください。

さらに探索

関連読書

その他の記事