Posted at

Qt5で、スタティックプラグインを使ったアプリケーションを作成する方法

More than 1 year has passed since last update.


はじめに

プラグインに対応したアプリケーションなら、本体側の開発と、拡張機能部分の開発を分離できるのでは、と思いQtを使ったプラグイン対応アプリケーションの作成方法を調べたのでまとめます。

プラグインには、ダイナミックプラグインとスタティックプラグインの2種類があります。

この記事では、スタティックプラグインの作り方をまとめます。


大まかな流れ

プラグインを受け入れる側(本体)の準備

1. プラグイン用のインターフェースクラスを作成する

2. Q_DECLARE_INTERFACEマクロで、インターフェースクラスと識別用のIDを関連づける

3. Q_IMPORT_PLUGINマクロで、インターフェースの実装クラス(プラグイン側)を、スタティックプラグインとしてインポートできるようにする

4. .proファイルにLIBS += <検索ディレクトリ> <リンクするプラグイン名>を追加して、プラグインをリンクする

5. QPluginLoader::staticInstances()を使って、指定の場所からプラグインをロードして使う

プラグイン側の準備

1. プロジェクトを、スタティックプラグインとしてビルドできるように、.proを加筆・修正

2. プラグインのメタデータ用にjsonファイルを作成する。中身はカラでも良い

2. 本体側で定義した、プラグイン用のインターフェースクラスの実装クラスを作成

3. この実装クラスに、Q_PLUGIN_METADATAQ_INTERFACESマクロを記述して、プラグインとしてエクスポートできるようにする


スタティックプラグインを使ったアプリケーションを作成する


アプリケーションの概要

ここでは、先に示した大まか流れに沿って、お絵描きアプリケーションを作っていきます。

お絵描きアプリケーションといっても、ペンツールと消しゴムツールしかない、極シンプルなものです。

この二つのツールを、スタティックプラグインとして作成します。

アプリケーションの構成(概略)

アプリケーションの構成.png

フォルダ構成

フォルダ構成.png


プラグインを受け入れる側(本体)の準備(SimplePaint)

プラグイン用のインターフェースクラスを作成する


ITool.h

#include <QtPlugin>


class ITool
{
...
};

#define ITool_iid "jp.tatsuteb.SimplePaint.ITool.v1"
Q_DECLARE_INTERFACE(ITool, ITool_iid)


プラグイン用のインターフェースクラスを作成したら、クラス定義の外側に Q_DECLARE_INTERFACE(Interface ClassName, ID) を記述します。QtPluginのインクルードも忘れずに行ってください。

ロードしたプラグインが、このインターフェースの実装を持っているかどうか照会するのに使われるようです。

インターフェースの実装クラスを、スタティックプラグインとしてインポートできるようにする


main.cpp

#include <QtPlugin>


Q_IMPORT_PLUGIN(PenToolPlugin)
Q_IMPORT_PLUGIN(EraserToolPlugin)

int main(int argc, char *argv[])
{
...
}


Q_IMPORT_PLUGIN(実装クラス名)を記述することで、スタティックプラグインを常に本体側で使えるようにします。

.proファイルを編集して、プラグインをリンクできるようにする


SimplePaint.pro

LIBS    += -L$$PWD/plugins/ -lsp_pentool -lsp_erasertool

if(!debug_and_release|build_pass):CONFIG(debug, debug|release) {
mac:LIBS = $$member(LIBS, 0) $$member(LIBS, 1)_debug $$member(LIBS, 2)_debug
win32:LIBS = $$member(LIBS, 0) $$member(LIBS, 1)d $$member(LIBS, 2)d
}


LIBSで、プラグインが格納されているディレクトリと、プラグイン名を指定します。-Lは検索するディレクトリ、-lはライブラリ名を示します。

ここでは、プラグイン名は「sp_pentool(ペンツール)」と「sp_erasertool(消しゴムツール)」にする予定なので、上記のような記述になっています。

ifの中でやっていることは、デバッグビルドとリリースビルドで、プラグインファイル名に_debugや_dがついたりつかなかったりするので、ビルドのタイプに応じてリンクするファイル名を変えています。

$$member(LIBS, 0) は LIBSの0番目の値を取得する、という意味になります。

QPluginLoader::staticInstances() を使って、指定の場所からプラグインをロードして使う


SimplePaint.cpp

#include <QPluginLoader>

#include "ITool.h"

...

QList<ITool *> SimplePaint::loadToolPlugins()
{
QList<ITool *> tools;
for (QObject *plugin : QPluginLoader::staticInstances())
{
// qobject_castはキャストに失敗すると0を返す
auto tool = qobject_cast<ITool *>(plugin);

// ITool以外のプラグインは無視
if (!tool)
{
continue;
}

tools << tool;
}

return tools;
}


QPluginLoaderは、LIBSで指定したスタティックプラグインを保持しているので、QPluginLoader::staticInstances()を使って、QObjectListとして取得します。QPluginLoaderのインクルードも忘れずに行ってください。

取得したプラグインはqobject_cast<インターフェースクラス *>(plugin)で、適切なクラスにキャストして使用します。キャストに失敗した場合は、ゼロが返ってきます。


プラグインの準備(SimplePaintPenToolPlugin)

プロジェクトを、スタティックプラグインとしてビルドできるように、.proを加筆・修正


SimplePaintPenToolPlugin.pro

QT  += core

TARGET = $$qtLibraryTarget(sp_pentool)
TEMPLATE = lib
CONFIG += plugin static
INCLUDEPATH += $$PWD/../../SimplePaint/
...
DESTDIR = $$PWD/../../SimplePaint/plugins


TARGET に出力するプラグインの名前を指定します。$$qtLibraryTarget(ファイル名)とすることで、デバッグビルドの時にmacでは_debug、Windowsでは_dがファイル名に付加されるようです。

TEMPLATEにはlibを指定します。

今回は、スタティックプラグインとしてビルドしたいので、CONFIGにはpluginとstaticを追加します。

INCLUDEPATHには、本体側に作成したインターフェースクラスが格納されているディレクトリを追加します。これにより、ITool.hがインクルードできるようになります。

DESTDIRには、プラグインを出力するディレクトリへのパスを追加します。

プラグインのメタデータ用にjsonファイルを作成する


PenTool.json

{}


プラグインのメタデータを記述したjsonファイルを作成します。今回は、中身はカラです。

本体側で定義した、プラグイン用インターフェースクラスの実装クラスを作成


PenToolPlugin.h

#include <QObject>

#include <QtPlugin>

#include "ITool.h"

class PenToolPlugin : public QObject, public ITool
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "jp.tatsuteb.SimplePaint.ITool.v1" FILE "PenTool.json")
Q_INTERFACES(ITool)

public:
...
};


Q_OBJECTの後に、Q_PLUGIN_METADATA(IID 本体側に作成したIDと同じもの FILE メタデータ用 jsonファイル)マクロを記述します。このプラグインをエクスポートできるようにします。

QtObjectとQtPluginのインクルードも忘れずに行います。

Q_INTERFACES(インターフェースクラス名)を記述して、基底クラスはプラグインインターフェースであることをmocに伝えます。

このマクロによりqobject_cast()でキャストできるようになるようです。


サンプル

GitHubに、線を描いて消すだけのサンプルを上げておきました。

GitHub - SimplePaint

各ツール(プラグイン)は、ToolPluginProjectsのサブプロジェクトになっています。


参考

主に、公式ドキュメントの「Plug & Paint」を参考にしました。こちらには、ダイナミックプラグインの作り方も詳しく書いてあります。

本体

Qt Documentation - Plug & Paint Example

スタティックプラグイン

Qt Documentation - Plug & Paint Basic Tools Example

ダイナミックプラグイン

Qt Documentation - Plug & Paint Extra Filters Example


おわりに

今回は、スタティックプラグインを使って、簡易お絵描きアプリのツールを実装しました。ところどころ、理解が足りていない部分があるので、今後修正するかもしれません…

スタティックプラグインなので、本体も一緒にビルドする必要があるわけですが、ダイナミックプラグインとして実装すれば、本体をビルドすることなくツールを開発できるので、その方が良かったのかな…と思いました。