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

# はじめに
プラグインに対応したアプリケーションなら、本体側の開発と、拡張機能部分の開発を分離できるのでは、と思い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_METADATA`、`Q_INTERFACES`マクロを記述して、プラグインとしてエクスポートできるようにする

# スタティックプラグインを使ったアプリケーションを作成する
## アプリケーションの概要
ここでは、先に示した大まか流れに沿って、お絵描きアプリケーションを作っていきます。
お絵描きアプリケーションといっても、ペンツールと消しゴムツールしかない、極シンプルなものです。
この二つのツールを、スタティックプラグインとして作成します。

**アプリケーションの構成（概略）**
![アプリケーションの構成.png](https://qiita-image-store.s3.amazonaws.com/0/141789/9d54954c-dee4-9964-7c4b-b3683145ef35.png)

**フォルダ構成**
![フォルダ構成.png](https://qiita-image-store.s3.amazonaws.com/0/141789/9d1bcf67-19f5-1b21-107c-4ee2354a2167.png)

## プラグインを受け入れる側（本体）の準備（SimplePaint）
**プラグイン用のインターフェースクラスを作成する**

```cpp: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のインクルードも忘れずに行ってください。
ロードしたプラグインが、このインターフェースの実装を持っているかどうか照会するのに使われるようです。


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

```cpp:main.cpp
#include <QtPlugin>

Q_IMPORT_PLUGIN(PenToolPlugin)
Q_IMPORT_PLUGIN(EraserToolPlugin)

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

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

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

```cpp: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() を使って、指定の場所からプラグインをロードして使う**

```cpp: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を加筆・修正**

```cpp: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ファイルを作成する**

```json:PenTool.json
{}
```

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


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

```cpp: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](https://github.com/tatsuteb/QtStaticPluginSample)
各ツール（プラグイン）は、ToolPluginProjectsのサブプロジェクトになっています。

## 参考
主に、公式ドキュメントの「Plug & Paint」を参考にしました。こちらには、ダイナミックプラグインの作り方も詳しく書いてあります。
**本体**
[Qt Documentation - Plug & Paint Example](http://doc.qt.io/qt-5/qtwidgets-tools-plugandpaint-app-example.html)
**スタティックプラグイン**
[Qt Documentation - Plug & Paint Basic Tools Example](http://doc.qt.io/qt-5/qtwidgets-tools-plugandpaint-plugins-basictools-example.html)
**ダイナミックプラグイン**
[Qt Documentation - Plug & Paint Extra Filters Example](http://doc.qt.io/qt-5/qtwidgets-tools-plugandpaint-plugins-extrafilters-example.html)

# おわりに
今回は、スタティックプラグインを使って、簡易お絵描きアプリのツールを実装しました。ところどころ、理解が足りていない部分があるので、今後修正するかもしれません…
スタティックプラグインなので、本体も一緒にビルドする必要があるわけですが、ダイナミックプラグインとして実装すれば、本体をビルドすることなくツールを開発できるので、その方が良かったのかな…と思いました。