Shared Object(.so)の勉強

いつの間にかはてなダイアリー市民になっていた。
立ち上げが去年の10月だったので1年以上かかったことになる。おせぇ。月に多くて5エントリぐらいだしな。
30日に1回は書いてないと剥奪されてしまうらしいので頑張って書いてみるぜ。


普段はPHPでWebシステムの開発をしているというのに、突然.soな共有ライブラリを扱った仕事が舞い込んできたのでお勉強。
C/C++自体はDOSWindowsでよくやっていたのだがLinux上での開発は初めてだ。
WinMain()から始まってメッセージループがあってWindows API使いまくり、みたいな開発は得意だがLinuxはさっぱりわからん。
同じ言語でも土俵が違うだけで結構辛い戦いが予想される。ていうか、OSの違い以前にGUICUI(今回はCUIどころかアプリでもない)ってだけでも全然違うのに、「C言語できるんでしょ?」みたいなのはやめてほしい。

Shared Objectを作ってみる

仕事で必要とされているのはライブラリではなく使う方のプログラムなのだが、肝心のライブラリがまだ手元にないので、とりあえず自分で作ってみることにする。
何せ.soを使ったプログラムというものが未経験(WindowsのDLLはわかる)なので、どうやって利用したらいいのかわからない。


ってことで、適当にぐぐって手順どおりにやってみた。

1.ライブラリ側のヘッダとソースを用意

とりあえずHello, World!な感じで、名前はhelloにした。
ライブラリ本体は頭にlibをつけるらしい。ヘッダは使う側を考慮してlibを外してみたが、どっちが正しい作法なのかわからん。

hello.h

#ifndef __HELLO_H__
#define __HELLO_H__

void say();

#endif

libhello.c

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

void say() {
    printf("Hello, World!\n");
}


我ながらあまりにも適当すぎる気がしてきたが、まぁいいや。
WindowsのDLLと違って共有ライブラリ特有のエントリポイントとかないのね(省略可能なだけで本当はあるのかもしれないが)。本当に関数が書いてあるだけ。

2.ライブラリをmakeする
gcc -fPIC -c libhello.c -o libhello.lo
gcc -shared -Wl,-soname,libhello.so libhello.lo -o libhello.so


オプションは書かれてある通りに指定。こんな意味らしい。

-fPIC
position-independent code(PIC)を作成する。
-c
コンパイルおよびアセンブルまででリンクはしない。要するにオブジェクトの生成まで。
-o
出力ファイルの明示的指定。これは大学時代にやったのでわかる!

ちなみにman gccによると-fPICと-fpicは違うようなのだが、自分にはよくわからなかった。
生成されるコードやロード時の配置に関係するのかなぁ、とか適当に想像。一般的には-fPICでいいらしい。


二行目。

-shared
共有オブジェクトの生成。
-Wl,option
optionをリンカに渡す。ここでは"-soname libhello.so"が渡される。


sonameというのはライブラリが持つ名前で、実行時にはこの名前で動的リンクが行われるらしい。
なので全然関係ないファイル名とかでもlibhello.soというsonameを持っていればそいつのリンクされる。
詳しいことはThe Linux GCC HOWTO「6.4 自分のライブラリを作る」あたりを見ればいいと思う。


とりあえずこれでlibhello.soという共有ライブラリができた。
Windowsみたいにコンパイル時のオプションに悩まなくていいので簡単だった。

3.ldconfigでシステムに認識させる

先のsonameの説明の通り、ファイル名ではなくsonameでリンクが決定されるような仕組みになっているので、その辺のことをシステムに認識させてやる必要がある。

ldconfig -n .


本来であれば/usr/libとかに置くんだが、こんなテストファイルは置きたくないので、-nでカレントディレクトリを対象に処理させる。

Shared Objectを使ってみる

ライブラリが出来たので早速使う側のプログラムを書いてみる。

1.使う側のソースを用意
#include "hello.h"

int main(int argc, char** argv) {
    say();
    return 0;
}


先ほどのhello.hをインクルードして、早速say()関数を呼んでいるだけ。

2.プログラムをmakeする
gcc -c -o hello.o hello.c
gcc -o hello -L. -lhello hello.o


オプションはこんな感じ。

-llibrary
リンクするライブラリ名を指定する。libを除いた名前を指定するのかな。
-Ldir
-lで検索が行われるディレクトリにdirを追加する。


まずはオブジェクトファイルを生成して、次に共有ライブラリをリンクしているという解釈でいいのかな。

  • lのところが微妙にわからなくて気になる。


man gccによると

リンカは、標準のライブラリ用ディレクトリのリスト中から、実際のファイル名が ‘liblibrary.a’ であるファイルを検索します。リンカはこのファイルを、ファイル名で直接指定した場合と同様に使用します。

って書いてあるんだけど、.aじゃないしなぁとか。


ところで、この辺の記述によると当然ファイル名でのリンクもできるわけで、こういうテストの場合はその方がよかったのかもしれないと思ったがもう遅かった。

3.実行してみる

なにはともあれ共有ライブラリもそれを使うプログラムを出来上がったので早速実行してみる。

LD_LIBRARY_PATH=. ./hello


ライブラリ本来の置き場所に.soを置いてないので、LD_LIBRARY_PATH環境変数にlibhello.soが存在するカレントディレクトリを指定して実行。

Hello, World!


キタレコ!
試しにlibhello.soをlibhello.xと名前を変えてみたり、消してみたりすると

./hello: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory

といった感じでライブラリのロードでエラーになるので、動的リンクしていることがわかる。

まとめ……というか感じたこと

ライブラリの作成はWindowsに比べれば妙な制約が少なくて簡単だった(複雑なものになってくると違うかもしれないけど)。
ただし、配置に関してはsonameとかldconfigとか色々ルールがあってWindowsよりも複雑だった。


使う側はどちらもリンク時にライブラリを指定すればよいので、同じようなもん……かと思ったけど、Windowsの場合はインポートライブラリが提供されてないとえらい苦労することを思い出した。


さて、このまま実行時の動的ロードも試したくなるわけだが、時間もないし今回は必要なさそうなのでまた今度。
WindowsのLoadLibrary/FreeLibrary()がdlopen/dlclose()、GetProcAddress()がdlsym()に相当すると考えればいいのかな。
C++だとシンボルの解決ではまるのも同じっぽい。