QMLの基礎 - ライブラリ

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

QML6およびC++を組み合わせて使用する場合、ライブラリを用いることにより、コードの再利用性、モジュール性、更新の容易さを向上させることができる。
ただし、プロジェクトの複雑性が増すため、デバッグや依存関係の管理に注意が必要である。

適切に設計および管理することにより、大規模なアプリケーションの開発効率を向上させることが可能となる。

ライブラリを使用するメリットを、以下に示す。

  • 再利用性
    ライブラリとして共通機能を分離することにより、他のプロジェクトでも同じライブラリを再利用できる。
  • モジュール性
    大規模なプロジェクトを複数のモジュールに分割することにより、コードの管理と保守が容易になる。
  • 更新の容易さ
    ライブラリを更新する場合、実行ファイルプロジェクトを再コンパイルする必要がなく、ライブラリを置き換えるだけで済む。
  • ロード時の選択性
    必要な時にのみライブラリを読み込むことにより、メモリ使用量を削減できる。
    また、動的ロードを活用してプラグイン機構を構築することも可能である。


ライブラリを使用するデメリットを、以下に示す。

  • 複雑性の増加
    動的ライブラリを使用することにより、プロジェクト構成やビルド設定が複雑になる。
    また、動的リンクに関する知識が必要である。
  • デバッグの難しさ
    ライブラリ内をデバッグする時、実行ファイルとライブラリの間で問題を追跡するのが難しくなることがある。
  • パフォーマンスオーバーヘッド
    ライブラリを動的ロードする時に、多少のパフォーマンスオーバーヘッドが発生する場合がある。
    ただし、これは通常ごく僅かである。
  • 依存関係の管理
    ライブラリのバージョン管理や依存関係の管理が必要になる。
    特に異なるバージョンのライブラリを使用するプロジェクト間での互換性を維持することが課題となる場合がある。



動的ライブラリ

動的ライブラリの作成

  1. QT Creatorのメイン画面から、[ファイル]メニューバー - [ファイル / プロジェクトの新規作成]を選択する。
  2. [新しいファイルまたはプロジェクト]画面左にある[ライブラリ]から[C++ Library]を選択する。
  3. [C++ Library]画面が開くので、以下の項目を設定する。
    • プロジェクト名やビルドシステム等を任意のものに設定する。
    • [プロジェクトの詳細定義]では、以下の手順で設定する。
      [Type:]は、[Shared Library]
      [Qt module:]は、Qtクラスを使用しない場合は[None]、Qtクラスを使用する場合は[Qt Core]、
      画面の作成が伴う場合は[Qt Gui]、ウィジェットの作成が伴う場合は[Widget]を選択する。
      [クラス名:]、[ソースファイル名:]、[ヘッダ名:]は任意のものに設定する。
    • [Translation File]や[キットの選択]は任意のものに設定する。


以下の例では、動的ライブラリとしてQMLのWindowコンポーネントを作成している。

 # CMakeLists.txtファイル
 
 # ...略
 
 project(LibSample LANGUAGES CXX)
 
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 set(CMAKE_AUTOMOC ON)
 
 find_package(Qt6 REQUIRED COMPONENTS Core Quick)
 
 add_library(LibSample SHARED
    LibSample_global.h
    LibSample.cpp
    LibSample.h
 )
 
 target_link_libraries(LibSample PRIVATE
    Qt6::Core
    Qt6::Quick
 )
 
 qt_add_qml_module(LibSample
    URI Lib
    VERSION 1.0
    QML_FILES LibSample.qml
 )
 
 target_compile_definitions(LibSample PRIVATE
    LIBSAMPLE_LIBRARY
 )


 // LibSample_global.hファイル
 
 #ifndef LIBSAMPLE_GLOBAL_H
 #define LIBSAMPLE_GLOBAL_H
 
 #if defined(_MSC_VER) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
    #define Q_DECL_EXPORT __declspec(dllexport)
    #define Q_DECL_IMPORT __declspec(dllimport)
 #else
    #define Q_DECL_EXPORT __attribute__((visibility("default")))
    #define Q_DECL_IMPORT __attribute__((visibility("default")))
 #endif
 
 #if defined(LIBSAMPLE_LIBRARY)
    #define LIBSAMPLE_EXPORT Q_DECL_EXPORT
 #else
    #define LIBSAMPLE_EXPORT Q_DECL_IMPORT
 #endif
 
 #endif // LIBSAMPLE_GLOBAL_H


 // LibSample.h
 
 #ifndef LIBSAMPLE_H
 #define LIBSAMPLE_H
 
 #include <QObject>
 #include <QString>
 #include <QQmlComponent>
 #include <QtQml/qqmlengine.h>
 #include "LibSample_global.h"
 
 class LIBSAMPLE_EXPORT LibSample : public QObject
 {
    Q_OBJECT
 
 public:
    explicit LibSample(QQmlEngine *engine, QObject *parent = nullptr);
    Q_INVOKABLE void showQmlWindow();
 
 private:
    QQmlEngine *m_engine;
 };
 
 #endif // LIBSAMPLE_H


 // LibSample.cpp
 
 #include <QQmlComponent>
 #include <QtQuick/QQuickWindow>
 #include "LibSample.h"
  
 LibSample::LibSample(QQmlEngine *engine, QObject *parent) : QObject(parent), m_engine(engine)
 {
    qDebug() << "LibSample lib";
 }
 
 void LibSample::showQmlWindow()
 {
    QQmlComponent component(m_engine, "qrc:/qt/qml/LibSample.qml");
 
    QObject *object = component.create();
    if (object) {
       QQuickWindow *window = qobject_cast<QQuickWindow*>(object);
       if (window) {
          window->show();
       }
    }
 }


 // LibSample.qmlファイル
 
 import QtQuick
 import QtQuick.Window
 import QtQuick.Controls
 import QtQuick.Layouts
 
 Window {
    id: libWindow
    width: 300
    height: 200
    visible: true
    title: qsTr("LibSample QML Window")
 
    ColumnLayout {
       anchors.fill: parent
       anchors.margins: 10
 
       Text {
          Layout.alignment: Qt.AlignHCenter
          text: "Hello from Hoge QML"
          font.pixelSize: 18
          color: "blue"
        }
 
        Button {
           Layout.alignment: Qt.AlignHCenter
           text: "Click Me"
           onClicked: {
              messageText.visible = true
              libWindow.close()
           }
        }
 
        Text {
           id: messageText
           Layout.alignment: Qt.AlignHCenter
           text: "Button was clicked!"
           color: "green"
           font.pixelSize: 16
           visible: false
        }
    }
 }


動的ライブラリのプロジェクトをビルドして、デバッグディレクトリまたはリリースディレクトリに動的ライブラリ (.soファイル) が生成される。

※注意
Windowsの場合、.dllファイルと.aファイルが生成されるため、.aファイルの.a拡張子を.lib拡張子に変更する必要がある。

動的ライブラリのリンク

動的ライブラリをQtプロジェクトにリンクするには、以下の手順を行う。

  1. Qt Creatorのメイン画面左の[プロジェクト]ペインから、プロジェクト名を右クリックして、[ライブラリを追加]を選択する。
  2. [ライブラリの追加]画面が開くので、以下の項目を設定する。
    • [ライブラリの種類]画面は、[外部ライブラリ]を選択する。
    • [外部ライブラリ]画面は、ライブラリのリンク方法とライブラリのパスを選択する。
      [ライブラリファイル:]項目は、動的ライブラリファイルのパスを入力する。(動的ライブラリの.soファイルまたは.aファイルを指定)
      [インクルードパス:]項目は、動的ライブラリのヘッダファイルが存在するパスを入力する。
      [リンク方法]は、[ダイナミック]を選択する。
  3. これにより、動的ライブラリとして、Qtプロジェクトにリンクすることができる。
    Qtプロジェクトのビルドは不要である。


次に、動的ライブラリの機能を使用するため、動的ライブラリのヘッダファイルをインクルードする。

  1. Qt Creatorのメイン画面左の[プロジェクト]ペインから、プロジェクト名を右クリックして、[既存のファイルの追加...]を選択する。
  2. ファイル選択ダイアログから、動的ライブラリのヘッダファイルを選択する。
    ヘッダファイルをインクルードすることにより、動的ライブラリの機能が使用できるようになる。


※注意
ただし、動的ライブラリはQT Creatorから独立して実行されるため、以下に示す手順を行う必要がある。

  • 動的ライブラリのパスを直接指定する場合
    まず、CMakeLists.txtファイルにおいて、target_link_librariesコマンドを使用して、.soファイルのパスを指定する。
    次に、target_include_directoriesコマンドを使用して、動的ライブラリが定義されているヘッダファイルが存在するディレクトリのパスを指定する。

  • find_packageコマンドを使用する場合
    また、動的ライブラリに.pcファイルが存在する場合、find_packageコマンドを使用してもよい。
    find_packageコマンドを使用する場合でも、
    target_include_directoriesコマンド (変数${xxx_INCLUDE_DIRS})、および、target_link_librariesコマンド (変数${xxx_LIBRARIES})を使用する必要がある。
 # CMakeLists.txtファイル
 
 # ...略
 
 project(exeApp LANGUAGES CXX)
 
 set(CMAKE_AUTOMOC ON)
  
 # ...略
 
 # ライブラリへのパスを指定
 target_link_libraries(exeSample PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../LibSample/${CMAKE_BUILD_TYPE}/libsample.so
 )
 
 target_include_directories(exeSample PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../LibSample
 )
 
 # ...略


 #include <QGuiApplication>
 #include <QQmlApplicationEngine>
 #include <QQmlContext>
 #include "LibSample.h"
 
 #include "app_environment.h"
 #include "import_qml_components_plugins.h"
 #include "import_qml_plugins.h"
 
 int main(int argc, char *argv[])
 {
    set_qt_environment();
 
    QGuiApplication app(argc, argv);
 
    QQmlApplicationEngine engine;
    const QUrl url(u"qrc:/qt/qml/Main/main.qml"_qs);
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app,
                     [url](QObject *obj, const QUrl &objUrl) {
                        if (!obj && url == objUrl)
                        QCoreApplication::exit(-1);
                     },
                     Qt::QueuedConnection);
 
    LibSample libSample(&engine);
    engine.rootContext()->setContextProperty("libSample", &libSample);
 
    engine.addImportPath(QCoreApplication::applicationDirPath() + "/qml");
    engine.addImportPath(":/");
 
    engine.load(url);
 
    if (engine.rootObjects().isEmpty()) {
       return -1;
    }
 
    return app.exec();
 }


 // main.qmlファイル
 
 import QtQuick
 import QtQuick.Window
 
 Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("QML from Library")
 
    Text {
       anchors.centerIn: parent
       text: libSample.showQmlWindow()
       wrapMode: Text.WordWrap
    }
 }


動的ライブラリの明示的リンク

Qtには、動的ライブラリの関数を簡単に使用するために、QLibraryクラスが用意されている。

明示的リンクを使用して、動的ライブラリの関数を呼び出す手順は、以下の通りである。

  1. 動的ライブラリのメソッドにおいて、C言語のグローバル関数 (Cスタイルのインターフェース) として定義することにより、ライブラリの動的ロードを容易にする。
  2. 実行ファイル側において、QLibraryクラスを使用して動的にライブラリを読み込み、動的ライブラリのクラスのインスタンスを生成する。
  3. 生成したインスタンスをQMLコンテキストにセットして、QMLファイルから動的ライブラリのクラスのメソッドを呼び出せるようにする。
  4. 実行ファイル側のQMLファイルから動的ライブラリのオブジェクトのメソッドを呼び出して、動的ライブラリ内のQMLファイルを表示する。


※注意
Windowsでは、動的ライブラリからメソッドを呼び出すには、__declspec(dllexport)を関数の前に付加すること。

上記セクションにある動的ライブラリの変更点のみを、以下に示す。

まず、動的ライブラリのクラスおよびメソッドの変更点を記述する。

 // LibSample.h (動的ライブラリ側)
 
 #ifndef LIBSAMPLE_H
 #define LIBSAMPLE_H
 
 #include <QObject>
 #include <QString>
 #include <QQmlComponent>
 #include <QtQml/qqmlengine.h>
 #include "LibSample_global.h"
 
 class LIBSAMPLE_EXPORT LibSample : public QObject
 {
    Q_OBJECT
 
 public:
    explicit LibSample(QQmlEngine *engine, QObject *parent = nullptr);
    Q_INVOKABLE void showQmlWindow();
 
 private:
    QQmlEngine *m_engine;
 };
 
 extern "C" {
    LibSample* createLibSample();
    void destroyLibSample(LibSample *instance);
 }
 
 #endif // LIBSAMPLE_H


 // LibSample.cpp (動的ライブラリ側)
 
 #include <QQmlComponent>
 #include <QtQuick/QQuickWindow>
 #include "LibSample.h"
  
 LibSample::LibSample(QQmlEngine *engine, QObject *parent) : QObject(parent), m_engine(engine)
 {
    qDebug() << "LibSample lib";
 }
 
 void LibSample::showQmlWindow()
 {
    QQmlComponent component(m_engine, "qrc:/Hoge/Hoge.qml");
 
    QObject *object = component.create();
    if (object) {
       QQuickWindow *window = qobject_cast<QQuickWindow*>(object);
       if (window) {
          window->show();
       }
    }
 }
 
 LibSample* createLibSample()
 {
    return new LibSample();
 }
 
 void destroyLibSample(LibSample *instance)
 {
    delete instance;
 }


次に、実行ファイル側 (動的ライブラリを使用する側) から動的ライブラリを読み込み、動的ライブラリのクラスのインスタンスを生成する。
生成したインスタンスをQMLコンテキストにセットして、QMLファイルから動的ライブラリのクラスのメソッドを呼び出す。

QLibraryクラスから呼び出す関数は、必ず、C言語のグローバル関数として定義すること。
これは、マングリングしていない元の名前から関数を呼び出すためである。

QLibraryクラスのコンストラクタには、.so拡張子または.dll拡張子を付加しないライブラリ名を渡す。
動的ライブラリをリンクするには、loadメソッドを実行する。

次に、resolveメソッドを使用して呼び出す関数名を渡して、関数が存在する場合は関数ポインタが返る。
メソッド名や型が間違っている場合は、0が返る。

もし、メソッド内でグローバル変数の値が変更された場合、次の関数を呼び出した時でも、グローバル変数の値を保持し続ける。

 // main.cpp (実行ファイル側)
 
 #include <QGuiApplication>
 #include <QQmlApplicationEngine>
 #include <QQmlContext>
 #include <QLibrary>
 #include "LibSample.h"
 
 using CreateLibSampleInstance = LibSample* (*)();
 using DestroyLibSampleInstance = void (*)(LibSample*);
 
 int main(int argc, char *argv[])
 {
    QGuiApplication app(argc, argv);
 
    QQmlApplicationEngine engine;
    const QUrl url(u"qrc:/main.qml"_qs);
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app,
                     [url](QObject *obj, const QUrl &objUrl) {
                        if (!obj && url == objUrl)
                           QCoreApplication::exit(-1);
                     }, Qt::QueuedConnection);
    engine.load(url);
 
    QLibrary lib("LibSample");
    if (lib.load()) {
       CreateLibSampleInstance createInstance = (CreateLibSampleInstance) lib.resolve("createLibSample");
       DestroyLibSampleInstance destroyInstance = (DestroyLibSampleInstance) lib.resolve("destroyLibSample");
 
       if (createInstance && destroyInstance) {
          LibSample *sample = createInstance();
          engine.rootContext()->setContextProperty("libSample", sample);
 
          QObject::connect(&app, &QCoreApplication::aboutToQuit, [sample, destroyInstance]() {
             destroyInstance(sample);
          });
        }
        else {
           qWarning() << "インスタンスメソッドの作成 / 破棄の解決に失敗";
        }
    }
    else {
       qWarning() << "動的ライブラリの読み込みに失敗";
    }
 
    return app.exec();
 }