動的にロードされる (dynamically loaded; DL) ライブラリは、 プログラムの起動時以外のときにロードされるライブラリです。 これはプラグインやモジュールを実装するのに特に役に立ちます。 というのは、プラグインが必要になるまで、 それをロードするのを待つことができるからです。例えば、 Pluggable Authentication Modules (PAM) システムは、 管理者が認証の設定や再設定をおこなえるようにするため、 DL ライブラリを使用しています。また、 全体を止めることなく、効率を上げる目的で、 その時々でコードをマシンコードにコンパイルし、 そのコンパイル後のものを使用するというインタプリタを実装するのにも役に立ちます。 この方法は、ジャストインタイムコンパイラや、マルチユーザダンジョン (multi-user dungeon; MUD) の実装時にも役に立ちます。
Linux では、実際のところ、DL ライブラリは形式という点においては特別ではありません。それらは、 標準的なオブジェクトファイル、 もしくは今までに述べたような標準的な共有ライブラリとして構築されています。 主な違いは、ライブラリが、 プログラムのリンク時や起動時に自動的にはロードされない、という点です。 その代わり、ライブラリをオープンし、シンボルを検索し、エラーを処理し、 ライブラリを閉じる、という API は存在します。この API を使うためには、 C ユーザはヘッダファイル <dlfcn.h> をインクルードする必要があります。
Linux によって使用されるインターフェースは本質的に Solaris 上のものと同じで、私が ``dlopen()'' API と呼ぼうとしているものです。 しかしながら、この同じインターフェースは全てのプラットフォームで サポートされているわけではありません。HP-UX は shl_load() という異なる機構を用いますし、Windows プラットフォームは完全に異なるインターフェースの DLL を使用します。 あなたの最終目標が広範なポータビリティならば、おそらく、 プラットフォーム間の差違を隠すラッピングライブラリの使用 を考えたほうがよいでしょう。一つのアプローチは、 モジュールの動的ローディングをサポートする glib ライブラリです。 これは、プラットフォームで土台となっている動的ローディング用ルーチンを使い、 それらの機能へのポータブルなインターフェースを実装します。glib については、 http://developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html を参照してください。 glib のインターフェースはそのドキュメントの中で十分に説明されているので、 ここではこれ以上は述べません。 もう一つのアプローチは、libltdl を使うことです。これは、 GNU libtool の一部です。もっと多くの機能を望むならば、 CORBA Object Request Broker (ORB) を調べてみるのもよいでしょう。 Linux と Solaris でサポートされるインターフェースを 直接使うことに依然として興味をお持ちならば、読み進んでください。
dlopen(3) 関数は、ライブラリをオープンし、使用するための準備をします。 C では、そのプロトタイプは次のようになります――
void * dlopen(const char *filename, int flag); |
ユーザの LD_LIBRARY_PATH 環境変数内のコロンで区切られたディレクトリリスト
/etc/ld.so.cache に指定されたライブラリリスト
/usr/lib, 次が /lib
ライブラリがお互いに依存しているようなら (例えば、X が Y に依存している) 、 依存されているほうを先にロードしてください (この例で言えば、Y を先にロードし、 それから X をロードします) 。
dlopen() の戻り値は、他の DL ライブラリルーチンで使用される ``ハンドル'' ――その実体は隠蔽されるぺきものと考えられている―― です。 ロードの試みが成功しない場合、dlopen() は NULL を返しますので、 この値をチェックする必要があります。 同じライブラリが dlopen() で二回以上ロードされると、 同じファイルハンドルが返されます。
もしもライブラリが _init という名前のルーチンをエクスポートしていれば、 そのコードは dlopen() が戻る前に実行されます。あなたのライブラリでも、 初期化ルーチンを実装するためにこれを使うことができます。 詳細は Section 5.2 を参照してください。
dlerror() を呼べば、エラーを報告できます。dlerror() は、 dlopen(), dlsym() もしくは dlclose() の最後の呼出しによるエラーについて記述してある文字列を返します。 一つ変わっているのは、dlerror() を呼び出すと、以降の dlerror() の呼出しは、 ほかのエラーが発生するまで NULL を返すという点です。
DL ライブラリが使えなければ、それをロードしても意味がありません。 DL ライブラリを使うための主となるルーチンは、dlsym(3) です。これは、 与えられた (オープン済みの) ライブラリ内にあるシンボルの値を検索するものです。 この関数は次のように定義されます――
void * dlsym(void *handle, char *symbol); |
dlsym() は、シンボルが見つからなければ NULL という結果を返します。 シンボルが NULL もしくはゼロという値をとることはありえないと分かっていれば、 それで構いません。しかし、そうでない場合は潜在的に曖昧さが残ります。 もしも NULL を受け取った場合、それは、 そんなシンボルは存在しないということを意味するのでしょうか、 もしくはそのシンボルの値が NULL であることを意味するのでしょうか? 標準的な解答は、dlerror() をはじめに呼び (存在しているかもしれない エラー条件をクリアするためです)、それから シンボルを要求するために dlsym() を呼び、エラーが発生しているかどうかを調べるために再度 dlerror() を呼び出すことです。 コードの断片は次のようになるでしょう――
dlerror(); /* エラーをクリアする */ s = (actual_type) dlsym(handle, symbol_being_searched_for); if ((err = dlerror()) != NULL) { /* ハンドルエラー。シンボルを見つけられなかった */ } else { /* シンボルが見つかった。その値は s に格納されている */ } |
dlopen() の逆が dlclose() で、これは DL ライブラリをクローズします。 dl ライブラリは動的なファイルハンドルへのリンク数を管理しているので、 同一動的ライブラリに対して、dlopen が成功した回数と同じ数の dlclose が呼ばれない限り、当該ライブラリは実際にはメモリ上から削除されません。 そのため、同じプログラムが同じライブラリを何回ロードしても、 問題にはなりません。 ライブラリの割当てが解除される場合は、(もしも存在するならば) _fini 関数が呼ばれます。 詳細は Section 5.2 を参照してください。
dlopen(3) の man ページからの例をここに載せます。 この例は、数学ライブラリをロードし、2.0 のコサインを出力し、また、 全てのステップでエラーをチェックしています (推奨されています) ――
#include <stdio.h> #include <dlfcn.h> int main(int argc, char **argv) { void *handle; double (*cosine)(double); char *error; handle = dlopen ("/lib/libm.so", RTLD_LAZY); if (!handle) { fputs (dlerror(), stderr); exit(1); } cosine = dlsym(handle, "cos"); if ((error = dlerror()) != NULL) { fputs(error, stderr); exit(1); } printf ("%f\n", (*cosine)(2.0)); dlclose(handle); } |
このプログラムが "foo.c" という名前のファイルだとすると、 次のコマンドでプログラムを作成することができます。
gcc -Wl,export-dynamic -o foo foo.c -ldl |
``-Wl,export-dynamic'' オプションは実際には必要ありませんが、 時々役に立つことがあります。ld(1) で次のように明記されています ――``ELF ファイルを作成しているとき、このオプションが、 全てのシンボルを動的シンボルテーブルに加えます。通常、動的シンボルテーブルは 動的オブジェクトによって使われるシンボルだけを含んでいます。 このオプションは dlopen の使用のために必要となります'' Linux システムだけで作業をしているならば、``-Wl,export-dynamic'' のかわりに ``-rdynamic'' を使えるけれども、ELF ドキュメントによれば、 非 Linux システム上の gcc では ``-rdynamic'' フラグは必ずしも機能しない、 ということには注意しておいてください。