Qtの基礎 - ライブラリ
概要
Qt Creatorにおいて、静的ライブラリおよび動的ライブラリを作成して、他のQtプロジェクトにリンクする手順を記載する。
静的ライブラリ
静的ライブラリの作成
- QT Creatorのメイン画面から、[ファイル]メニューバー - [ファイル / プロジェクトの新規作成]を選択する。
- [新しいファイルまたはプロジェクト]画面左にある[ライブラリ]から[C++ Library]を選択する。
- [C++ Library]画面が開くので、以下の項目を設定する。
- プロジェクト名やビルドシステム等を任意のものに設定する。
- [プロジェクトの詳細定義]では、以下の手順で設定する。
- [Type:]は、[Shared Library]
- [Qt module:]は、Qtクラスを使用しない場合は[None]、Qtクラスを使用する場合は[Qt Core]、
- 画面の作成が伴う場合は[Qt Gui]、ウィジェットの作成が伴う場合は[Widget]を選択する。
- [クラス名:]、[ソースファイル名:]、[ヘッダ名:]は任意のものに設定する。
 
 
- [Translation File]や[キットの選択]は任意のものに設定する。
プロジェクトをビルドして、静的ライブラリを生成する。
生成されたライブラリは、Windowsの場合は.lib拡張子、Linuxの場合は.a拡張子である。
静的ライブラリのリンク
静的ライブラリを他のQtプロジェクトにリンクするには、以下の手順を行う。
- 他のQtプロジェクトにおいて、Qt Creatorのメイン画面にある[プロジェクト]ペインからプロジェクト名を右クリックして、
 [ライブラリを追加]を選択する。
- [ライブラリの追加]画面が開くので、以下の項目を設定する。
 - [ライブラリの種類]では、[外部ライブラリ]を選択する。
- [外部ライブラリ]では、ライブラリのリンク方法とライブラリのパスを選択する。
 [ライブラリファイル:]は、ライブラリが存在するパスを入力する。
 [リンク方法]は、[スタティック]を選択する。
 
- Qtプロジェクトをビルドすることにより、静的ライブラリをリンクすることができる。
動的ライブラリ
動的ライブラリの作成
- QT Creatorのメイン画面から、[ファイル]メニューバー - [ファイル / プロジェクトの新規作成]を選択する。
- [新しいファイルまたはプロジェクト]画面左にある[ライブラリ]から[C++ Library]を選択する。
- [C++ Library]画面が開くので、以下の項目を設定する。
- プロジェクト名やビルドシステム等を任意のものに設定する。
- [プロジェクトの詳細定義]では、以下の手順で設定する。
- [Type:]は、[Shared Library]
- [Qt module:]は、Qtクラスを使用しない場合は[None]、Qtクラスを使用する場合は[Qt Core]、
- 画面の作成が伴う場合は[Qt Gui]、ウィジェットの作成が伴う場合は[Widget]を選択する。
- [クラス名:]、[ソースファイル名:]、[ヘッダ名:]は任意のものに設定する。
 
- [Translation File]や[キットの選択]は任意のものに設定する。
 
以下の例では、動的ライブラリとしてダイアログ(QDialogの派生クラス)を作成している。
 #ifndef LIBDIALOG_GLOBAL_H
 #define LIBDIALOG_GLOBAL_H
 
 #include <QtCore/qglobal.h>
 
 #ifdef Q_OS_WIN   // Windows
    #if defined(LIBDIALOG_LIBRARY)
       #define LIBDIALOG_EXPORT __declspec(dllexport)
    #else
       #define LIBDIALOG_EXPORT
    #endif
 #elif Q_OS_LINUX  // Linux
    #if defined(LIBDIALOG_LIBRARY)
       #define LIBDIALOG_EXPORT Q_DECL_EXPORT
    #else
       #define LIBDIALOG_EXPORT Q_DECL_IMPORT
    #endif
 #endif
 
 #endif // LIBDIALOG_GLOBAL_H
 // LibDialog.h
 
 #ifndef LIBDIALOG_H
 #define LIBDIALOG_H
 
 #include "LibDialog_global.h"
 #include <QDialog>
 
 class LIBDIALOG_EXPORT LibDialog : public QDialog
 {
 public:
    LibDialog();
 };
 
 #endif // LIBDIALOG_H
 // LibDialog.cpp
 
 #include "LibDialog.h"
 
 LibDialog::LibDialog()
 {
    setWindowTitle("Show dialog");
 }
動的ライブラリのプロジェクトをビルドして、debugディレクトリまたはreleaseディレクトリにライブラリが生成される。
※注意
Windowsの場合、DLLファイルとaファイルが生成されるため、
aファイルの.a拡張子を.lib拡張子に変更する必要がある。
動的ライブラリのリンク
動的ライブラリを他のQtプロジェクトにリンクするには、以下の手順を行う。
- 他のQtプロジェクトにおいて、Qt Creatorのメイン画面にある[プロジェクト]ペインから、
 プロジェクト名を右クリックして、[ライブラリを追加]を選択する。
- [ライブラリの追加]画面が開くので、以下の項目を設定する。
 - [ライブラリの種類]では、[外部ライブラリ]を選択する。
- [外部ライブラリ]では、ライブラリのリンク方法とライブラリのパスを選択する。
- [ライブラリファイル:]は、動的ライブラリが存在するパスを入力する。(動的ライブラリの.libファイルまたは.aファイルを指定)
- [リンク方法]は、[ダイナミック]を選択する。
 
- [ライブラリファイル:]は、動的ライブラリが存在するパスを入力する。(動的ライブラリの
 
- これにより、動的ライブラリとして、Qtプロジェクトにリンクすることができる。
 Qtプロジェクトのビルドは不要である。
次に、動的ライブラリの機能を使用するため、動的ライブラリのヘッダファイルをインクルードする。
- 動的ライブラリの機能を使用するため、Qt Creatorのメイン画面からにある[プロジェクト]ペインから、
 プロジェクト名を右クリックして、[既存のファイルの追加...]を選択する。
- ファイル選択ダイアログから、動的ライブラリのヘッダファイルを追加する。
 ヘッダファイルをインクルードすることにより、動的ライブラリの機能が使用できるようになる。
- ただし、動的ライブラリはQT Creatorから独立して実行されるため、実行ファイルと同じディレクトリに動的ライブラリを配置する必要がある。
 #include <QCoreApplication>
 #include "LibDialog.h"
 
 int main(int argc, char * argv[])
 {
    QCoreApplication a(argc, argv);
 	
    LibDialog dialog;
    dialog.show();
 	
    return a.exec();
 }
動的ライブラリの明示的リンク
Qtには、動的ライブラリの関数を簡単に使用するために、QLibraryクラスが用意されている。
明示的リンクを使用して、動的ライブラリの関数を呼び出す手順は、以下の通りである。
- 動的ライブラリの関数において、C言語のグローバル関数として定義する。
- 動的ライブラリを生成する。
- 動的ライブラリを呼び出す側の実行ファイルと同じディレクトリに、動的ライブラリを配置する。
- QLibraryクラスを使用して、動的ライブラリの関数を呼び出す。
以下の構成を持つ2つのプロジェクトを作成したとする。
- プロジェクト構成
- 動的ライブラリ
- Utils
 
- 実行ファイル
- Moc
 
 
- 動的ライブラリ
まず、Utilsプロジェクトにおいて、関数のみを定義したUtils.cppファイルを作成する。
 // Utils_global.h
 
 #ifndef UTILS_GLOBAL_H
 #define UTILS_GLOBAL_H
 
 #include <QtCore/qglobal.h>
 
 #ifdef Q_OS_WIN   // Windows
    #if defined(UTILS_LIBRARY)
       #define UTILS_EXPORT __declspec(dllexport)
    #else
       #define UTILS_EXPORT
    #endif
 #elif Q_OS_LINUX  // Linux
    #if defined(UTILS_LIBRARY)
       #define UTILS_EXPORT Q_DECL_EXPORT
    #else
       #define UTILS_EXPORT Q_DECL_IMPORT
    #endif
 #endif
 
 #endif // LIBDIALOG_GLOBAL_H
 // Utils.cpp
 
 #include "Utils_global.h"
 #include <iostream>
 
 //#ifdef Q_OS_WIN
 //   #define UTILS_EXPORT __declspec(dllexport)
 //#else
 //   #define UTILS_EXPORT
 //#endif
 
 extern "C" UTILS_EXPORT void SampleHello(int count)
 {
    while(--count)
    {
       fprintf(stdout, "Hello!!");
    }
 }
次に、Mocプロジェクト(動的ライブラリを使用する側)から、動的ライブラリの関数を呼び出す。
QLibraryクラスから呼び出す関数は、必ず、C言語のグローバル関数として定義すること。
これは、マングリングしていない元の名前から関数を呼び出すためである。
※注意
Windowsでは、動的ライブラリから関数を呼び出すには、__declspec(dllexport)を関数の前に付加すること。
QLibraryクラスのコンストラクタには、.so拡張子または.dll拡張子を付加しないライブラリ名を渡す。
動的ライブラリをリンクするには、loadメソッドを実行する。
次に、resolveメソッドを使用して呼び出す関数名を渡して、関数が存在する場合は関数ポインタが返る。
関数名や型が間違っている場合は、0が返る。
もし、関数内でグローバル変数の値が変更された場合、次の関数を呼び出した時でも、グローバル変数の値を保持し続ける。
 // main.cpp
 
 #include <QApplication>
 #include <QLibrary>
 using SampleFunc = void (*)(int);
 
 int main(int argc, char * argv[])
 {
    QApplication a(argc, argv);
 
    // 動的ライブラリをリンクする
    QLibrary lib("Utils");
    lib.load();
 
    // 動的ライブラリの関数を取得する
    SampleFunc Func =  (SampleFunc)lib.resolve("SampleHello");
 
    if(Func != 0)
    {
       Func(10);
    }
 
    return a.exec();
 }
ライブラリとバイナリ互換性
バイナリ互換性とは
大規模なソフトウェアの場合、複数の機能を再利用したり、プラグイン等を開発するためにライブラリを使用することが多い。
静的ライブラリまたは動的ライブラリをリンクしてソフトウェアから機能を利用する。
しかし、ライブラリを開発する上で、機能の追加または削除した場合、バイナリ互換性の問題が発生することがある。
バイナリ互換性とは、ライブラリとそれをリンクしたソフトウェアの間に互換性があるかということである。
例えば、以下のようなライブラリを開発したとする。
 // Plugin_global.h
 
 #ifndef PLUGIN_GLOBAL_H
 #define PLUGIN_GLOBAL_H
 
 #include <QtCore/qglobal.h>
 
 #ifdef Q_OS_WIN   // Windows
    #if defined(PLUGIN_LIBRARY)
       #define PLUGIN_EXPORT __declspec(dllexport)
    #else
       #define PLUGIN_EXPORT
    #endif
 #elif Q_OS_LINUX  // Linux
    #if defined(PLUGIN_LIBRARY)
       #define PLUGIN_EXPORT Q_DECL_EXPORT
    #else
       #define PLUGIN_EXPORT Q_DECL_IMPORT
    #endif
 #endif
 
 #endif // PLUGIN_GLOBAL_H
 // Plugin.h
 
 #ifndef PLUGIN_H
 #define PLUGIN_H
 
 #include "Plugin_global.h"
 
 class PLUGIN_EXPORT Plugin
 {
 public:
    Plugin();
    virtual ~Plugin();
 
 private:
    int version;
 };
 
 #endif // PLUGIN_H
 // Plugin.cpp
 
 #include "Plugin.h"
 
 Plugin::Plugin()
 {
 }
 
 Plugin::~Plugin()
 {
 }
以下に、上記のライブラリをリンクするソフトウェアを記述する。
 // main.h
 #include <iostream>
 
 // Pluginクラスを継承
 class MyPlugin : public Plugin
 {
 public:
    MyPlugin() : version(0)
    {
    }
 
    ~MyPlugin()
    {
    }
 	
    void printVersion()
    {
       std::cout << "MyPlugin version : " << version << std::endl;
    }
 	
 private:
    int myVersion;
 };
 // main.cpp
 
 #include <iostream>
 
 int main()
 {
    MyPlugin plugin;
    plugin.printVersion();
 	
    return 0;
 }
次に、以下の例のように、ライブラリの機能を追加したとする。
機能を追加したライブラリを生成して、それを利用側のソフトウェアに再リンクする時、エラーを出力してクラッシュする。
(利用側のソフトウェアも再ビルドする場合は、正常に読み込まれる)
これは、派生クラスのメモリレイアウトが変更されたからである。 (クラスの機能を追加することにより、メンバ変数またはメンバ関数のオフセット値が変更される)
 // Plugin.h
 
 #ifndef PLUGIN_H
 #define PLUGIN_H
 
 #include "Plugin_global.h"
 #include <string>
 
 class PLUGIN_EXPORT Plugin
 {
 public:
    Plugin();
    virtual ~Plugin();
 
 private:
    int version;
    std::string author;
 };
 
 #endif  // PLUGIN_H
バイナリ互換性を保つ方法
バイナリ互換性を保つ方法として、Pimplイディオムを使用する。
Pimplは、元々プライベートの実装を隠蔽するために使用されるイディオムである。
バイナリ互換性を保つためにPimplを使用する理由として、
変数を1つのクラスにまとめて、後からソースコードを変更してもオフセット値が変化しない利点がある。
以下の例では、Implクラスを使用することで、Pluginクラスに存在したプライベートメンバの実装をまとめて隠蔽している。
このように、メンバ変数およびメンバ関数を他クラス(ここでは、Implクラス)にまとめて記述することで、
後から変更があってもクラッシュすることは無い。
これが、Pimplを使用したバイナリ互換性の保ち方である。
 // Impl.h
 
 #ifndef IMPL_H
 #define IMPL_H
 
 #include <string>
 
 class Impl
 {
 public:
    int version;
    std::string author;
 };
 
 #endif  // IMPL_H
 // Plugin_global.h
 
 #ifndef PLUGIN_GLOBAL_H
 #define PLUGIN_GLOBAL_H
 
 #include <QtCore/qglobal.h>
 
 #ifdef Q_OS_WIN   // Windows
    #if defined(PLUGIN_LIBRARY)
       #define PLUGIN_EXPORT __declspec(dllexport)
    #else
       #define PLUGIN_EXPORT
    #endif
 #elif Q_OS_LINUX  // Linux
    #if defined(PLUGIN_LIBRARY)
       #define PLUGIN_EXPORT Q_DECL_EXPORT
    #else
       #define PLUGIN_EXPORT Q_DECL_IMPORT
    #endif
 #endif
 
 #endif // PLUGIN_GLOBAL_H
 // Plugin.h
 
 #ifndef PLUGIN_H
 #define PLUGIN_H
 
 #include "Plugin_global.h"
 #include "Impl.h"
 #include <memory>
 
 class PLUGIN_EXPORT Plugin
 {
 public:
    Plugin();
    virtual ~Plugin();
 
 private:
    std::unique_ptr<Impl> pImpl;
 };
 
 #endif  // PLUGIN_H
 // Plugin.cpp
 
 #include "Plugin.h"
 #include "Impl.h"
 #include <memory>
 
 Plugin::Plugin() : pImpl(std::make_unique<Impl>())
 {
 }
 
 Plugin::~Plugin()
 {
 }