qmake を使った動的ライブラリの作成

このページは,qmake を使った共有ライブラリの作成を引き継ぎ,qmake6 を使って動的ライブラリを作成したり利用法したりする方法を紹介します.
素材は,端末に「Hello world.」と出力する定番のプログラムです.C++ 言語を使っています.

このページは,プログラミング -自作ソフトウェアと環境- ページのプログラミングの練習セクションの「Hello World もの」のひとつです.

インフォメーション

目次(ページ内リンク)


動的ライブラリの作成(グローバル関数編)
動的ライブラリの利用(グローバル関数編)
動的ライブラリの作成(クラス編)
動的ライブラリの利用(クラス編)

参考にしたページ


Creating Shared Libraries | Qt 6.4
qmake Manual

インストールした Debian パッケージ

パッケージ名(バージョン)
g++(12.2.0)GNU C++ コンパイラ
make (4.3)コンパイルを制御するユーティリティ
qt6-base-dev(6.4.2)Qt 6 用の開発ツール.このページ後半の Hello World をおこなうには,qmake6 パッケージ単独ではなく,基本的な開発環境が必要です

「Creating Shared Libraries」について

注.
理解が不充分なのですが,このページの後のセクションでも試しているように,「Creating Shared Libraries」で言う Shared Libraries とは,プログラム実行時に必要が生じた際にリンクされるライブラリ(動的ライブラリ,あるいは Windows の dll)に限定されるのかもしれません.プログラム起動時にリンクされる共有ライブラリでは考慮しなくても動作しました.

Creating Shared Libraries | Qt 6.4には,共有ライブラリの作成や利用に際しての注意点が記されています.
私抄訳してみました.

アプリケーションや他のライブラリといったクライアントから利用されるシンボル(例えば,共有ライブラリの公開メンバ関数の名前)は,公開シンボルと呼ばれます.
共有ライブラリをコンパイルする際には,公開シンボルを export でマークする必要があります.
クライアントから共有ライブラリを使用するには,プラットフォームによっては,特別な import 宣言が必要です.
プラットフォームに応じて,Qt は必要な定義を含む特別なマクロを提供しています.

共有ライブラリをコンパイルする場合でも,共有ライブラリを使用する場合でも,コンパイル時には正しいマクロが呼び出されるようにする必要があります.これは特別なヘッダを追加することで解決できます.
例えば,mysharedlib という名前の共有ライブラリを作成するとすると,下のようなヘッダファイル mysharedlib_global.h を作成します.

mysharedlib_global.h


#include <QtCore/QtGlobal>

#if defined(MYSHAREDLIB_LIBRARY)
#  define MYSHAREDLIB_EXPORT Q_DECL_EXPORT
#else
#  define MYSHAREDLIB_EXPORT Q_DECL_IMPORT
#endif

そうして,ライブラリの各ヘッダでは次のように指定します.


#include "mysharedlib_global.h"

MYSHAREDLIB_EXPORT void foo();
class MYSHAREDLIB_EXPORT MyClass...

また,共有ライブラリのプロジェクトファイル .pro に下の行を追加します.


DEFINES += MYSHAREDLIB_LIBRARY

以下では,C++ による共有ライブラリ,動的ライブラリの作成で作成したコードを素材にし,qmake を使ってライブラリをビルドする際のプロジェクトファイルの書き方を検討しています.


動的ライブラリの作成(グローバル関数編)

端末に「Hello World.」と出力するグローバル関数 void hello() を作成し,動的ライブラリにします.
この場合,「Createing Shared Library」の記述を取り入れてパブリックシンボルの処理をする必要があります.
さらに,グローバル関数を動的ライブラリにする場合は,名前修飾を解決するための「extern "C"」宣言も必要です.これは Qt ではなくて C++ の仕様です.
これらがないと,実行ファイルで動的呼び出しをするとセグメンテーション違反となります.

ソースファイル

3 個のソースファイルを作業用のディレクトリ qlibhello に作成しました.qlibhello.pro は,作業中に作成,編集します

qlibhello
├─ (qlibhello.pro)
├─ global.h
├─ hello.h
└─ hello.cc

global.h

Qt で動的ライブラリ用の Makefile を作成する場合,パブリックシンボル(ライブラリ外部から利用されるクラスや関数などの名前)を明示する必要があります.
パブリックシンボルが存在することを記述するヘッダファイルが Creating Shared Libraries | Qt 6.4 に紹介されています.
それがこの global.h です.
ここでは,オリジナルの MYSHAREDLIB を,ここで作成しているライブラリ HELLOLIB に変更しています.


#include <QtCore/QtGlobal>

#if defined(HELLOLIB_LIBRARY)
#  define HELLOLIB_EXPORT Q_DECL_EXPORT
#else
#  define HELLOLIB_EXPORT Q_DECL_IMPORT
#endif

hello.h


#ifndef ___HELLO
#define ___HELLO

#include "global.h"
#include <iostream>

extern "C" HELLOLIB_EXPORT void hello();

#endif

hello.cc


#include "hello.h"

void hello() {
 std::cout << "Hello World." << std::endl;
}

プロジェクトファイル

雛形の作成コマンドは共有ライブラリの作成(グローバル関数編)と同じく,
~/qlibhello$ qmake6 -project
です.編集後の qlibhello.pro を示します.

qlibhello.pro


TEMPLATE = lib
TARGET = hello
INCLUDEPATH += .

DEFINES += HELLOLIB_LIBRARY

HEADERS += greet.h
SOURCES += hello.cc

headersDataFiles.files = greet.h
headersDataFiles.path = /usr/local/include
INSTALLS += headersDataFiles

target.path = /usr/local/lib
INSTALLS += target

global.h はインストールしていないのですが,ライブラリは利用できました.この理由はよくわかりません.

ビルドとインストール

ソースファイルを置いたディレクトリで,
~/qlibhello$ qmake6
~/qlibhello$ make
~/qlibhello$ sudo make install
~/qlibhello$ sudo ldconfig
としました.


動的ライブラリの利用(グローバル関数編)

ソースファイル

ソースファイルを作業用のディレクトリ qexehello に作成しました.qexehello.pro は,作業中に作成,編集します

qexehello
├─ (qexehello.pro)
└─ main.cc

main.cc


#include <dlfcn.h>

int main() {
 void* handle = dlopen("libhello.so", RTLD_LAZY);
 typedef void (*hello_t)();
 hello_t hello = (hello_t) dlsym(handle, "hello");
 hello();
 dlclose(handle);

 return 0;
}

プロジェクトファイル

雛形の作成コマンドは共有ライブラリの利用(グローバル関数編)セクションと同じく,
~/qexehello$ qmake6 -project
です.編集後のプロジェクトファイルを示します.

qexehello.pro


TEMPLATE = app
TARGET = hello
SOURCES += main.cc

LIBS += -L/usr/local/lib/ -lhello
INCLUDEPATH += /usr/local/include

target.path = /usr/local/bin
INSTALLS += target

ビルド,インストール,実行

ソースファイルを置いたディレクトリで,
~/qexehello$ qmake6
~/qexehello$ make
~/qexehello$ sudo make install
~/qexehello$ hello
とすれば,端末に「Hello World.」と表示されました.


動的ライブラリの作成(クラス編)

qmake の使い方に関しては「動的ライブラリの作成(グローバル関数編)」と同じです.
動的クラスライブラリは多態性を利用するなど,このページのレベルとしては多少煩雑です.そのため,参考として記しています.
動的クラスライブラリの作成法は,C++ による共有ライブラリ,動的ライブラリの作成を参照してください.

ソースファイル

ソースファイルを作業用のディレクトリ qlibhello に作成します.qlibhello.pro は,作業中に作成,編集します

qlibhello
├─ (qlibhello.pro)
├─ global.h
├─ greet.h
└─ hello.cc

global.h

動的ライブラリの作成(グローバル関数編)と同じものです.説明を含め再掲します.

Qt で動的ライブラリ用の Makefile を作成する場合,パブリックシンボル(ライブラリ外部から利用されるクラスや関数などの名前)を明示する必要があります.
パブリックシンボルが存在することを記述するヘッダファイルが Creating Shared Libraries | Qt 6.4 に紹介されています.
それがこの global.h です.
ここでは,オリジナルの MYSHAREDLIB を,ここで作成しているライブラリ HELLOLIB に変更しています.


#include <QtCore/QtGlobal>

#if defined(HELLOLIB_LIBRARY)
#  define HELLOLIB_EXPORT Q_DECL_EXPORT
#else
#  define HELLOLIB_EXPORT Q_DECL_IMPORT
#endif

greet.h

動的なクラスライブラリの作成には多態性を利用します.
ヘッダファイルでは,純粋仮想メンバ関数をもつ基底クラスを宣言します.それがこの greet.h です.


#ifndef ___GREET_CLASS
#define ___GREET_CLASS

class Greet_Class {
 public:
  Greet_Class() {}
  virtual ~Greet_Class() {}
  virtual void hello_func() const = 0;
};

typedef Greet_Class* load_greet_symbol();
typedef void destroy_greet_symbol(Greet_Class*);

#endif

hello.cc

動的なクラスライブラリの実装ファイルでは,純粋仮想メンバ関数をもつ基底クラスの派生クラスを定義します.
それから,クラスファクトリ関数と呼ばれる 2 つのヘルパー関数を追加定義します.これらの関数の 1 つは,クラスのインスタンスを生成し,そのポインタを返します.もう 1 つの関数は,クラスへのポインタを受け取り,それを破棄します.これら2つの関数は extern "C " として修飾されています.


#include "global.h"
#include "greet.h"
#include <iostream>

class Hello_Class : public Greet_Class {
 public:
  virtual void hello_func() const {
   std::cout << "Hello World." << std::endl;
  }
};

extern "C" HELLOLIB_EXPORT Greet_Class* load_hello_symbol() {
    return new Hello_Class;
}

extern "C" HELLOLIB_EXPORT void destroy_hello_symbol(Greet_Class* p) {
    delete p;
}

プロジェクトファイル

qlibhello.pro

プロジェクトファイルの作成コマンドは共有ライブラリの作成(グローバル関数編)セクションと同じく,
~/qlibhello$ qmake6 -project
です.編集後の内容を示します.
動的ライブラリの作成(グローバル関数編)とはヘッダファイルの名前が異なるだけです(hello.h ではなくて greet.h).
headersDataFiles. に記したように,ヘッダファイルは /usr/local/include にインストールされるように設定しました.global.h はインストールしていません.
global.h をインストールしていないのですが,ライブラリは利用できました.この理由はよくわかりません.


TEMPLATE = lib
TARGET = hello
INCLUDEPATH += .

DEFINES += HELLOLIB_LIBRARY

HEADERS += greet.h
SOURCES += hello.cc

headersDataFiles.files = greet.h
headersDataFiles.path = /usr/local/include
INSTALLS += headersDataFiles

target.path = /usr/local/lib
INSTALLS += target

ビルドとインストール

ソースファイルを置いたディレクトリで,
~/qlibhello$ qmake6
~/qlibhello$ make
~/qlibhello$ sudo make install
~/qlibhello$ sudo ldconfig
としました.


動的ライブラリの利用(クラス編)

このセクションについても qmake の使い方に関しては「動的ライブラリの作成(グローバル関数編)」と同じです.
動的クラスライブラリは多態性を利用するなど,このページのレベルとしては多少煩雑です.そのため,参考として記しています.
動的クラスライブラリの利用法は,C++ による共有ライブラリ,動的ライブラリの作成を参照してください.

ソースファイル

ソースファイルを作業用のディレクトリ qexehello に作成します.qexehello.pro は,作業中に作成,編集します

qexehello
├─ (qexehello.pro)
└─ main.cc

main.cc


#include <dlfcn.h>
#include <greet.h>

int main() {
 void* hello_handle = dlopen("libhello.so", RTLD_LAZY);

 load_greet_symbol* load_hello_symbol
   = (load_greet_symbol*) dlsym(hello_handle, "load_hello_symbol");

 destroy_greet_symbol* destroy_hello_symbol
   = (destroy_greet_symbol*) dlsym(hello_handle, "destroy_hello_symbol");

 Greet_Class* hello_instance = load_hello_symbol();

 hello_instance->hello_func();

 destroy_hello_symbol(hello_instance);

 dlclose(hello_instance);

 return 0;
}

qexehello.pro


TEMPLATE = app
TARGET = hello
SOURCES += main.cc

LIBS += -L/usr/local/lib/ -lhello
INCLUDEPATH += /usr/local/include

target.path = /usr/local/bin
INSTALLS += target

ビルド,インストール,実行

ソースファイルを置いたディレクトリで,
~qexehello qmake6
~qexehello make
~qexehello sudo make install
~qexehello hello
とすれば,端末に「Hello World.」と表示されました.


参考書の検索