概要
Qt Creatorにおいて、静的ライブラリおよび動的ライブラリを作成して、他のQtプロジェクトにリンクする手順を記載する。
外部リンケージの制限
--version-scriptオプションによるAPI制限
マップファイルに公開する関数を指定して、コンパイラのビルドオプション (LDFLAGS
) に-Wl,--version-script,<マップファイル名>.map
を付加する。
LDFLAGS+=-Wl,--version-script,<マップファイル名>.map
<マップファイル名>.map { global: <公開する関数名 1>; <公開する関数名 2>; <公開する関数名 3>; local: *; };
公開する関数に__attribute__((visibility("default")))を付加する
コンパイラのビルドオプションに-fvisibility=hidden
を付加する場合、まず全ての関数が非公開になる。
次に、公開する関数名の前に__attribute__((visibility("default")))
を付加する。
__attribute__((visibility(...)))
が付加されることにより、
除外されるものを除いて、コンパイルで生成される全てのグローバルシンボルに指定された動的な可視性のタイプを属性を付ける。
外部リンケージの確認
nmコマンドの使用
nm
コマンドを実行して、外部リンケージを確認することができる。
nm -D <ライブラリファイル名> # または nm -D --defined-only <ライブラリファイル名>
静的ライブラリ
QMakeを使用する場合
変数TEMPLATE
にlib
、変数CONFIG
にlib
またはstaticlib
を指定する必要がある。
TEMPLATE = lib
CONFIG += staticlib
静的ライブラリの作成
- 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>
#if define(Q_OS_WIN) // Windows
#if defined(LIBDIALOG_LIBRARY)
#define LIBDIALOG_EXPORT __declspec(dllexport)
#else
#define LIBDIALOG_EXPORT
#endif
#elif defined(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 Creatorのメイン画面左の[プロジェクト]ペインから、プロジェクト名を右クリックして、[ライブラリを追加]を選択する。
- [ライブラリの追加]画面が開くので、以下の項目を設定する。
- [ライブラリの種類]画面は、[外部ライブラリ]を選択する。
- [外部ライブラリ]画面は、ライブラリのリンク方法とライブラリのパスを選択する。
- [ライブラリファイル:]項目は、動的ライブラリファイルのパスを入力する。(動的ライブラリの
.so
ファイルまたは.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>
#if defined(Q_OS_WIN) // Windows
#if defined(UTILS_LIBRARY)
#define UTILS_EXPORT __declspec(dllexport)
#else
#define UTILS_EXPORT
#endif
#elif defined(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>
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>
#if defined(Q_OS_WIN) // Windows
#if defined(PLUGIN_LIBRARY)
#define PLUGIN_EXPORT __declspec(dllexport)
#else
#define PLUGIN_EXPORT
#endif
#elif defined(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>
#if defined(Q_OS_WIN) // Windows
#if defined(PLUGIN_LIBRARY)
#define PLUGIN_EXPORT __declspec(dllexport)
#else
#define PLUGIN_EXPORT
#endif
#elif defined(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()
{
}
静的ライブラリと動的ライブラリの切り替え
静的ライブラリは、他のプロジェクトに組み込んで使用できるのに対して、動的ライブラリは、実行時に動的にリンクされる違いがある。
しかし、プロジェクトによっては、静的ライブラリと動的ライブラリのビルドを切り替えたい場合もある。
以下に、静的ライブラリと動的ライブラリのビルドを切り替える方法を記載する。
まず、動的ライブラリ(ここではMyLib)としてプロジェクトを作成する。
この時、以下のようなプロジェクトファイルが生成される。
# ...略 TARGET = MyLib TEMPLATE = lib DEFINES += MYLIB_LIBRARY CONFIG += c++17 SOURCES += MyLib.cpp HEADERS += MyLib.h\ mylib_global.h # ...略
上記のままでは、ビルド時に動的ライブラリしか生成されない。
そのため、上記のプロジェクトファイルを以下のように変更する。
MAKE_MYLIB_DLL
マクロを定義して、値がtrue以外の場合、静的ライブラリを生成するように設定している。
# ...略 TARGET = MyLib TEMPLATE = lib DEFINES += MYLIB_LIBRARY CONFIG += c++17 MAKE_MYLIB_DLL = false # trueの場合は動的ライブラリを生成する # falseの場合は静的ライブラリを生成する equals(MAKE_MYLIB_DLL, true) { DEFINES += MYLIB_LIBRARY DEFINES += DEFINE_SHARED_EXPORT } else { CONFIG += staticlib } # ...略
次に、グローバルヘッダファイル(xxx_global.h)を変更する。
DEFINED_SHARED_EXPORT
マクロが定義されていない場合、MYLIBSHARED_EXPORT
マクロを未定義にしているため、動的ライブラリは生成されない。
この設定のみで、静的ライブラリと動的ライブラリの切り替えができる。
#ifndef MYLIB_GLOBAL_H
#define MYLIB_GLOBAL_H
#include <QtCore/qglobal.h>
#ifdef Q_OS_WIN // Windows
#if defined(MYLIB_LIBRARY)
#define MYLIBSHARED_EXPORT __declspec(dllexport)
#else
#define MYLIBSHARED_EXPORT
#endif
#elif Q_OS_LINUX // Linux
#if defined(MYLIB_LIBRARY)
#define MYLIBSHARED_EXPORT Q_DECL_EXPORT
#else
#define MYLIBSHARED_EXPORT Q_DECL_IMPORT
#endif
#endif
#ifndef DEFINE_SHARED_EXPORT
#define MYLIBSHARED_EXPORT
#endif
#endif // MYLIB_GLOBAL_H