void *dlopen(const char *filename, int flag);
char *dlerror(void);
void *dlsym(void *handle, const char *symbol);
int dlclose(void *handle);
そのライブラリが他の共有ライブラリに依存している場合は、 依存しているライブラリも動的リンカーが同じ検索ルールに基づいて 自動的にロードする (それらのライブラリにさらに依存関係がある場合などは この処理は再帰的に行われる)。
flag には以下の 2 つの値のいずれかを含めなければならない:
以下の値のうち 0 個以上を論理和 (OR) の形で flag に追加することもできる:
filename が NULL である場合は、 返されるハンドルはメインプログラムのものになる。 このハンドルが dlsym() に渡されると、シンボルの検索は、メインプログラム内、 プログラムの起動時にロードされる全ての共有ライブラリ、 dlopen() によって RTLD_GLOBAL フラグ付きでロードされた全ての共有ライブラリ、の順序で行われる。
オープンされたライブラリ中での外部参照は、 そのライブラリの依存リストにあるライブラリか、 RTLD_GLOBAL フラグ付きで既にオープンされているライブラリを使って解決される。 実行ファイルが "-rdynamic" フラグ ("--export-dynamic" も同義) 付きでリンクされている場合は、実行ファイル中のグローバルシンボルも、 動的にロードされるライブラリ内の参照解決に用いられる。
同じライブラリが dlopen() によって再度ロードされた場合には、同じライブラリハンドルが返される。 dl ライブラリはライブラリハンドルのリンク数を管理している。 したがって動的ライブラリは dlclose() が dlopen() と同じ回数だけ呼び出されない限りアンロードされない。 _init() ルーチンは一度だけ呼び出される (_init() が存在する場合のみ)。 RTLD_NOW が指定されて dlopen() が呼び出された場合、 RTLD_LAZY で以前にロードされたライブラリのシンボル解決が実行されることがある。
dlopen() は、何らかの理由で失敗すると NULL を返す。
RTLD_DEFAULT と RTLD_NEXT という二つの特別な擬似ハンドルがある。 RTLD_DEFAULT は、デフォルトのライブラリ検索順序にしたがって、 検索対象のシンボルが最初に現れるところを探す。 RTLD_NEXT は、ライブラリ検索順序の中で現在のライブラリ以降で最初に 関数が現れるところを探す。この機能を使うことで、別の共有ライブラリの 関数へのラッパーを提供することができる。
関数 dlclose() は、成功した場合は 0 を返し、エラーの場合 0 以外を返す。
このルーチンや、gcc のオプション -nostartfiles や -nostdlib は使用しないことを推奨する。 これらを使うと、望ましくない動作をすることがある。 なぜなら、(特別な措置が行われない限り) これらの constructor/destructor ルーチンは実行されないからである。
代わりに、ライブラリは __attribute__((constructor)) や __attribute__((destructor)) の関数属性を使って必要なルーチンをエクスポートするのがよい。 これらについては gcc の info ページを参照のこと。 constructor ルーチンは dlopen() が復帰する前に実行され、 destructor ルーチンは dlclose() が復帰する前に実行される。
#define _GNU_SOURCE /* feature_test_macros(7) 参照 */ #include <dlfcn.h> int dladdr(void *addr, Dl_info *info); void *dlvsym(void *handle, char *symbol, char *version);
関数 dladdr() は、関数のポインターを引き数にとり、関数の名前と関数が定義されている ファイルの解決を試みる。情報は Dl_info 構造体に格納される。
typedef struct {
const char *dli_fname; /* Pathname of shared object that
contains address */
void *dli_fbase; /* Address at which shared object
is loaded */
const char *dli_sname; /* Name of symbol whose definition
overlaps addr */
void *dli_saddr; /* Exact address of symbol named
in dli_sname */
} Dl_info;
addr にマッチするシンボルが見つからなかった場合、 dli_sname と dli_saddr は NULL にセットされる。
dladdr() は、エラー時には 0 を返し、成功した場合は 0 以外を返す。
関数 dlvsym() は dlsym() と同じ動作をするが、バージョンの文字列を渡す引き数が 追加されている点が異なる (dlvsym() はバージョン 2.1 以降の glibc で提供されている)。
glibc 2.2.3 以降では、 atexit(3) を使って、ライブラリがアンロードされる際に自動的に呼び出される 終了ハンドラー (exit handler) を登録することができる。
問題は、関数ポインターの解決は今なおコンパイル時に行われるが、 そのポインターは元のオブジェクトの plt (Procedure Linkage Table) セクションを指しているだけだという点にある (オブジェクト自体は、ダイナミックリンカーによってシンボルの解決が行われた後に、 関数の呼び出しを行う)。 これに対処する方法としては、 コードを position-independent でコンパイルするという方法がある。 そうすると、コンパイラはコンパイル時にポインターを用意することができず、 今日の gcc(1) では、実行時に dladdr() に関数ポインターを渡す前に、 got (Global Offset Table) から最終的なシンボルのアドレスをロードするだけの コードが生成される。
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int
main(int argc, char **argv)
{
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen("libm.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlerror(); /* Clear any existing error */
cosine = (double (*)(double)) dlsym(handle, "cos");
/* ISO の C 標準によれば、上のような、関数ポインターと 'void *' 間の
キャストを行った場合に得られる結果は不定である。
POSIX.1-2003 と POSIX.1-2008 では、この状況は認められており、
以下のようなワークアラウンドが提案されている。
*(void **) (&cosine) = dlsym(handle, "cos");
この (ぶかっこうな) キャストは ISO の C 標準に従っており、
コンパイラの警告を避けることができる。
POSIX.1-2008 の 2013 Technical Corrigendum (別名 POSIX.1-2013)
では、 POSIX に準拠する実装では 'void *' から関数ポインターへの
キャストをサポートすることが要求されるようになり、状況が改善
された。にもかかわらず、('-pedantic' オプションを指定した gcc
などの) いくつかのコンパイラは、このプログラムで使用されている
キャストについて文句を言うのだ。
error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
printf("%f\n", (*cosine)(2.0));
dlclose(handle);
exit(EXIT_SUCCESS);
}
このプログラムを "foo.c" に書いたとすると、以下のコマンドでプログラムを ビルドできる。
gcc -rdynamic -o foo foo.c -ldl
_init() と _fini() をエクスポートするライブラリの場合は 以下のようにしてコンパイルする必要がある。 例として bar.c をコンパイルする場合:
gcc -shared -nostartfiles -o bar bar.c
ld.so info pages, gcc info pages, ld info pages