C++ による共有ライブラリの作成

このページは,C++ 言語で共有ライブラリを作成する方法を勉強したときのノートです,
素材は,端末に「Hello world.」と出力する定番のプログラムです.
このページは,ライブラリの勉強としては C++ による動的ライブラリの作成Autotools を使ったライブラリのビルドシステムに発展します.実践的なプログラミングとしては分子モデリングライブラリ libbuilcule の基礎になっています.

インフォメーション

目次(ページ内リンク)

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

Debian パッケージ

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

パッケージ名(バージョン)
g++ (14.2.0)GNU C++ コンパイラ(このページ改定開始前にインストール済みでした)
make-doc (4.4.1)パッケージ化されたドキュメント.HTML ファイルが /usr/share/doc/make-doc/make.html/ にインストールされました

定番の実行ファイル

プログラミング入門の定番,端末に 「Hello World.」 と出力する実行ファイル hello を作成します,
後で Makefile も手作りしてみます,

ソースコード

下は実行ファイル hello のソースファイル main.cc です,

main.cc

#include <iostream>

int main() {
 std::cout << "Hello World." << std::endl;
 return 0;
}

ビルドとインストール

ここでの作業は単純で,端末にコマンドを 3 行入力するだけです,
なお,ここではコンパイルとリンクを合わせてビルドと言います,

ソースファイル main.cc をコンパイルして,オブジェクトファイル main.o を作成します,
$ g++ -c main.cc
オブジェクトファイル main.o をリンク(ここでは main.o しかありませんが,一般に,複数のオブジェクトファイルやライブラリと)して実行ファイル hello を生成します,
$ g++ -o hello main.o
実行ファイル hello を /usr/local/bin/ にインストールします,これはスーパーユーザー権限で.
$ sudo install -s hello /usr/local/bin/

実行

端末に
$ ./hello
と入力したら,

Hello World.

と表示されました,どうやら成功したようです,


共有ライブラリ作成のための予備知識

共有ライブラリは,プログラムの一部を独立させて名前をつけて別ディレクトリに置いたものです.それゆえ,

が可能です,
言い換えると,これらを実現するためには,共有ライブラリのバージョンやインストールされたパスを管理したうえで,実行ファイルと適切にリンクしてやる必要があります,
これらの処理は,共有ライブラリのファイル名と ldconfig コマンドでおこないます,

予備知識その 1:共有ライブラリのファイル名

Linux を扱い始めると,語幹が同じで語尾が異なるだけのライブラリファイル (lib□□□.so.△△△ というやつ) が出てくるので戸惑います,
似た名前のファイルが多数存在するのは,シンボリックリンクを作成して,一つのライブラリ(== ファイル)にいろいろな名前を与えているためです,
シンボリックリンクを利用してバージョンごとに語尾の番号を変えれば,異なるバージョンのライブラリを共存させることが可能になります,
Program Library HOWTO の「3. 共有ライブラリ」では,共有ライブラリに与える名前として,主として次に記す 3 個が説明されています,

real name が実際のファイルの名前で,他は real name へのシンボリックリンクです,
マイナー番号とリリース番号は,どのバージョンのライブラリがインストールされているかを正確に示すもので,設定管理に役立つとのことです,

同じものを別の表現で書くと,

予備知識その 2:ldconfig コマンド

ldconfig の機能として,「3. 共有ライブラリ」では下の 2 点が挙げられています,

「既に存在するファイル」を検索するパスは,/etc/ld.so.conf 以下に記述されています.
ライブラリをインストールする際や,ライブラリを未設定の検索パスにインストールするときに,
ライブラリのパスを /etc/ld.so.conf 以下に記述したり,シェルの設定ファイル(.bashrc など)に記述し,
$ sudo ldconfig
を実行しますが,これは上述の作業を行っているわけです.
この作業により,プログラムの実行時にライブラリを利用できるようになります.

linker name に関する説明を抜粋しておきます,
「ldconfig は linker name を作成しません,これは,古いライブラリをリンクしたい場合を想定しているからです,
通常,この作成作業はライブラリインストール時におこない,単純に,「最新の」soname もしくは最新の real name へのシンボリックリンクとして linker name を作成します,
ほとんどの場合において,ライブラリを更新したら,リンク時にそれを自動的に利用したいと思うでしょうから,soname へのシンボリックリンクとして linker name を作っておくことをお勧めします,」

キャッシュファイル /etc/ld.so.cache はバイナリファイルです.
ldconfig コマンドには,この内容を表示するオプションもあります.すなわち,
$ sudo ldconfig -p
で内容が端末に標準出力されます.下はその末尾です.

    libAvogadroCalc.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libAvogadroCalc.so.1
    ld-linux-x86-64.so.2 (libc6,x86-64) => /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
Cache generated by: ldconfig (Debian GLIBC 2.36-9+deb12u7) stable release version 2.36

共有ライブラリの作成

上で作成した,端末に「Hello World.」と出力するプログラムから関数 void hello() を独立させて共有ライブラリとします.

ソースコード

下にソースコードを記します.
目的とする関数 void hello() をグローバル関数とします.
関数の宣言を記したヘッダファイル hello.h と関数の定義を記したファイル hello.cc を別ファイルとします,
共有ライブラリを使って実行ファイルを作成するときに,このヘッダファイルを使います.

hello.h

#ifndef ___HELLO
#define ___HELLO

void hello();

#endif
hello.cc

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

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

コンパイル

共有ライブラリに組み込むオブジェクトファイルを,gcc の -fPIC または -fpic フラグを使って作成します,
このオプションにより,共有ライブラリに対する要求事項である「位置独立コード (position independent code)」を生成することができます
$ g++ -c hello.cc -fPIC
hello.o が生成します.

ヘッダファイルを置くためのディレクトリを新たに作成すると,「hello.cc:1:10: fatal error: hello.h: そのようなファイルやディレクトリはありません」といったメッセージが表示される場合があります.
そういう場合は,ヘッダファイルが置かれたパスを I オプションで明示します,
$ g++ -c hello.cc -fPIC -I/usr/local/include/

リンク

オブジェクトファイルから共有ライブラリを作成します,
$ g++ -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0.0 hello.o
libhello.so.1.0.0 が生成します.
ちなみに,「3. 共有ライブラリ」には,オブジェクトファイルが a.o と b.o の 2 個ある場合には,上の書式の末尾を a.0 b.o -lc にすると書いてあります,

共有ライブラリのインストール

「3. 共有ライブラリ」には,インストールの簡単な方法として,標準的なディレクトリにそのライブラリをコピーし,ldconfig を実行する,
と説明されています,やってみます,

まず,/usr/local/lib にインストールします.
$ sudo install libhello.so.1.0.0 /usr/local/lib/

次いで,linker name をファイル名とする,libhello.so.1.0.0 へのシンボリックリンク libhello.so を作成する必要があります,
そうしないと,実行ファイルを作成するときリンカが認識できません,
$ sudo ln -sf /usr/local/lib/libhello.so.1.0.0 /usr/local/lib/libhello.so

最後に,ldconfig を実行します,
$ sudo ldconfig
/usr/local/lib/ に,soname をファイル名とする,libhello.so.1.0.0 へのシンボリックリンク libhello.so.1 が作成されていました,


$ sudo ldconfig -n /usr/local/lib/
と,-n オプションを使うとシンボリックリンクは作成されますが,キャッシュは再構築されません,
キャッシュを再構築するには,もう一度 ldconfig を実行する必要があります,
$ sudo ldconfig

インストールされたファイルをまとめると,

異なるバージョンを共存させる場合には,バージョン番号を設定すれば良いということです,

ヘッダファイルのインストール

実行ファイルをビルドする際に必要なので,ヘッダファイルを適切なディレクトリにコピーしておきます,
(ビルドに必要ということは,実行ファイルは共有ライブラリの仕様を予め知っておかねばならないということ)
ここでは,/usr/local/include にコピーしました,
$ sudo cp hello.h /usr/local/include/


共有ライブラリの利用

上で作成した共有ライブラリを利用する実行ファイルを作成し,実行してみます,

ソースコード

ソースコードを示します,
共有ライブラリのヘッダをインクルードして,共有ライブラリの関数 hello() を呼び出しています,

main.cc

#include <hello.h>

int main() {
 hello();
 return 0;
}

ビルドとインストール

実行ファイルのビルドは,
$ g++ -c main.cc・・・コンパイル,libhello1.0.0 の soname が参照される
$ g++ -o hello main.o -lhello・・・リンク,libhello1.0.0 の linker name が参照される

実行ファイルのインストールは,
$ sudo install hello /usr/local/bin/
とします.

実行

端末に
$ ./hello
と入力したら,

Hello World.

と表示されました,どうやら成功したようです,