C++ による動的ライブラリの作成
このページは,C++ 言語で動的ライブラリを作成する方法を勉強したときのノートです,
C++ による共有ライブラリの作成では,端末に "Hello World." と出力するグローバル関数を改変して共有ライブラリとしました.
このページではその共有ライブラリを改変し,プログラムが必要とする時に読み込まれるライブラリ,動的ライブラりを作成します.
Autotools を使ったライブラリのビルドシステムでは,ここで作成したソースコードを使っています.
インフォメーション
目次(ページ内リンク)
参考にしている主なページ
- Program Library HOWTO:C 言語を使ったライブラリ作成法の 4. 動的ライブラリ
- C++ dlopen mini HOWTO:C++ 言語で dlopen() を利用する方法を中心とする解説
「4. 動的ライブラリ」は C 言語用のドキュメントなので,そのままでは使えません,C++ で使うために,C++ dlopen mini HOWTO というドキュメントを読みました,
インストールした Debian パッケージ
このページは,Debian GNU/Linux バージョン 13 "trixie" で作成しました.
使用している主なパッケージを示します.
| パッケージ名(バージョン) | 注 |
| g++ (14.2.0) | GNU C++ コンパイラ |
動的ライブラリの作成(グローバル関数編)
オンラインドキュメントからの情報
C 言語による動的ライブラリの場合は,4. 動的ライブラリに書いてあります.
まずライブラリをオープンして,そのなかにあるシンボル(ポインタ)を取得して利用し,ライブラリを閉じる,という流れで利用します.
そのために,動的ライブラリのオープンからクローズまでに 3 個,エラー処理を加えると 4 個の関数を使っています.
- dlopen():ライブラリをオープンし,使用前の準備をおこないます.戻り値は,動的ライブラリ・ルーチンによって使用される「ハンドル」です
- dlerror():エラーを報告できます
- dlsym():オープン済みのライブラリ内にあるシンボルの値を検索します
- dlclose():動的ライブラリをクローズします
「C++ dlopen mini HOWTO」によると,
C 言語では動的ライブラリを利用する際には dlopen(),dlym(),dlclose() を呼び出すだけですが,C++ 言語では,次の点が原因で状況が異なります.
- Name Mangling(名前修飾)により多重定義やオーバーロードが可能になっている.そのせいで,そのままだと dlopen() がどの関数を呼べばいいのか判定できない
- dlopen() API が C 言語を念頭に置いて書かれており,動的クラスライブラリをロードするのに適した方法を提供していない
このため,C++ 言語のライブラリを動的にロードする手順が C 言語より煩雑になっています.
何にせよ,以上の要素を共有ライブラリのコードに加味すれば,動的ライブラリが作成できるはずです.
動的ライブラリを作成するためのコード
関数ライブラリの場合は,名前修飾を解決するために extern "C" 宣言を使います,
「C++ dlopen mini HOWTO」によると,extern "C" として宣言された関数は,C言語の関数と同じように関数名をシンボル名として使用します(C++ のような名前修飾をしない).
extern "C" として宣言できるのは非メンバー関数のみであり,オーバーロードができません.
下に示すのがソースコードで,C++ による共有ライブラリの作成のコードとは extern "C" 宣言が追加されていることのみ異なります.
hello.h
#ifndef ___HELLO
#define ___HELLO
extern "C" void hello();
#endif
hello.cc
#include "hello.h"
#include <iostream>
void hello() {
std::cout << "Hello World." << std::endl;
}
ビルトとインストール
動的ライブラリのビルドとインストールは,共有ライブラリの場合と同じです.
簡単にまとめると,
コンパイル
$ g++ -c hello.cc -fPIC
ヘッダファイルを置くディレクトリを指定する場合は,I オプションを使います.
$ g++ -c hello.cc -fPIC -I/usr/local/include/
hello.o が生成します.
リンク
$ g++ -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0.0 hello.o
libhello.so.1.0.0 が生成します.
ライブラリのインストール
$ sudo install libhello.so.1.0.0 /usr/local/lib/
$ sudo ln -sf /usr/local/lib/libhello.so.1.0.0 /usr/local/lib/libhello.so
/usr/local/lib/libhello.so.1.0.0,/usr/local/lib/libhello.so.1,および /usr/local/lib/libhello.so が生成します.
ライブラリの検索パスは,/etc/ld.so.conf 記述されています.
/usr/local/lib をパスに加える簡単な方法は,/etc/ld.so.conf に /usr/local/lib 行を記述し,
$ sudo ldconfig
を実行することですす.
この作業により,プログラムの実行時にライブラリを利用できるようになります.
ヘッダのインストール
$ sudo cp hello.h /usr/local/include/
動的ライブラリの利用(グローバル関数編)
動的ライブラリを利用するコード
下のコードは,「C++ dlopen mini HOWTO」のサンプルコードからエラー処理(dlerror()の利用)を削除したものです,
動的呼び出しする際には,上で作成したライブラリに含まれる hello() 関数への関数ポインタを利用しています.
main.cc
#include <dlfcn.h>
int main() {
//ライブラリに含まれる void hello() と同じ型である,関数ポインタ hello_t 型を宣言しておく
typedef void (*hello_t)();
//動的ライブラリをオープンしてハンドルを取得
void* handle = dlopen("libhello.so", RTLD_LAZY);
//ハンドルから void hello() へのポインタのシンボルを取得し,hello_t 型の関数ポインタ hello_ptr に代入
hello_t hello_ptr = (hello_t) dlsym(handle, "hello");
//関数ポインタ hello_ptr を利用する関数呼び出し
hello_ptr();
//動的ライブラリをクローズ
dlclose(handle);
return 0;
}
ビルドとインストール
まずコンパイル.
$ g++ -c main.cc
動的ライブラリを利用する実行ファイルをリンクする際には,-ldl オプションを使用します.
$ g++ -o hello main.o -ldl
最後にインストール.
$ sudo install hello /usr/local/bin
実行
$ hello
とすると,端末に Hello World. と出力されました.
動的ライブラリの作成(クラス編)
動的なクラスライブラリの作成には多態性を利用します.すなわち,
- ヘッダファイルでは,純粋仮想メンバ関数をもつ基底クラスを宣言します
- 実装ファイルでは,その派生クラスを定義します
それから,クラスファクトリ関数と呼ばれる 2 つのヘルパー関数を追加定義します.
- これらの関数の 1 つは,クラスのインスタンスを生成し,そのポインタを返します
- もう 1 つの関数は,クラスへのポインタを受け取り,それを破棄します
- これら2つの関数は extern "C " として修飾されています
「C++ dlopen mini HOWTO」に記されている注意点を引用します.
- クラスファクトリ関数は,作成関数と破棄関数の両方を提供しなければなりません
- 実行ファイルの中でインスタンスを生成,破棄するときは,必ずクラスファクトリ関数を使ってください.C++ では new と delete という演算子がオーバーロードされている可能性があるためです
- 基底クラスのデストラクタは,どんな場合でも仮想的であるべきです
- 基底クラスがデストラクタを必要としない場合は,空の(そして仮想の)デストラクタを定義してください
「C++ dlopen mini HOWTO」では「polygon クラス」という基底クラス,「triangle クラス」という継承クラスを作成し,三角形の面積を計算しています.
それを参考にして,基底クラスである「Greet_Class」,派生クラスである「Hello_Class」を作成し,Hello_Class で端末に「Hello World.」と出力する関数を実装しました.
ソースコード
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
#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" Greet_Class* load_hello_symbol() {
return new Hello_Class;
}
extern "C" void destroy_hello_symbol(Greet_Class* p) {
delete p;
}
ビルドとインストール
前述のグローバル関数編と同じです.
$ g++ -c hello.cc -fPIC
$ g++ -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0.0 hello.o
$ sudo install libhello.so.1.0.0 /usr/local/lib/
$ sudo ln -sf /usr/local/lib/libhello.so.1.0.0 /usr/local/lib/libhello.so
$ sudo ldconfig
$ sudo cp greet.h /usr/local/include/
動的ライブラリの利用(クラス編)
下のコードは,「C++ dlopen mini HOWTO」のサンプルコードを Hello_Class 向けに改変したもので,エラー処理をしていません,
ソースコード
main.cc
#include <greet.h>
#include <dlfcn.h>
int main() {
//上でインストールした libhello をロードする
void* hello_handle = dlopen("libhello.so", RTLD_LAZY);
//クラスファクトリ関数その 1.Hello_Class のインスタンスへのポインタを返す関数のシンボルを取得する
load_greet_symbol* load_hello_symbol
= (load_greet_symbol*) dlsym(hello_handle, "load_hello_symbol");
//クラスファクトリ関数その 2.Hello_Class のインスタンスへのポインタを破棄する関数のシンボルを取得する
destroy_greet_symbol* destroy_hello_symbol
= (destroy_greet_symbol*) dlsym(hello_handle, "destroy_hello_symbol");
//Hello_Class のインスタンスを生成する.クラスファクトリ関数その 1 を利用
Greet_Class* hello_instance = load_hello_symbol();
//Hello_Class のインスタンスを使う
hello_instance->hello_func();
//Hello ライブラリをアンロードする
dlclose(hello_instance);
//Hello_Class のインスタンスを破棄する.クラスファクトリ関数その 2 を利用
destroy_hello_symbol(hello_instance);
return 0;
}
ビルド,インストール,実行
前述のグローバル関数編と同じです.
$ g++ -c main.cc
$ g++ -o hello main.o -ldl
$ sudo install hello /usr/local/bin
$ hello
とすると,端末に Hello World. と出力されました.