C++ による動的ライブラリの作成

このページは,C++ 言語で動的ライブラリを作成する方法を勉強したときのノートです,
C++ による共有ライブラリの作成では,端末に "Hello World." と出力するグローバル関数を改変して共有ライブラリとしました.
このページではその共有ライブラリを改変し,プログラムが必要とする時に読み込まれるライブラリ,動的ライブラりを作成します.

Autotools を使ったライブラリのビルドシステムでは,ここで作成したソースコードを使っています.

インフォメーション

目次(ページ内リンク)

参考にしている主なページ

「4. 動的ライブラリ」は C 言語用のドキュメントなので,そのままでは使えません,C++ で使うために,C++ dlopen mini HOWTO というドキュメントを読みました,

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

このページは,Debian GNU/Linux バージョン 13 "trixie" で作成しました.
使用している主なパッケージを示します.

パッケージ名(バージョン)
g++ (14.2.0)GNU C++ コンパイラ

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

オンラインドキュメントからの情報

C 言語による動的ライブラリの場合は,4. 動的ライブラリに書いてあります.
まずライブラリをオープンして,そのなかにあるシンボル(ポインタ)を取得して利用し,ライブラリを閉じる,という流れで利用します.
そのために,動的ライブラリのオープンからクローズまでに 3 個,エラー処理を加えると 4 個の関数を使っています.

「C++ dlopen mini HOWTO」によると,
C 言語では動的ライブラリを利用する際には dlopen(),dlym(),dlclose() を呼び出すだけですが,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 つのヘルパー関数を追加定義します.

「C++ dlopen mini HOWTO」に記されている注意点を引用します.

「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. と出力されました.