7
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

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_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

おわりに

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
7
Help us understand the problem. What are the problem?