dlopen() の利用方法
Cygwin 環境における dlopen() の利用方法と、環境変数
LD_LIBRARY_PATH の挙動に関して解説した文書です。
環境変数 LD_LIBRARY_PATH とは
UNIX 系 OS では共有ライブラリを探し出す場所を指定するための手段として、環境変数
LD_LIBRARY_PATH が用意されています。
UNIX 系 OS では、共有ライブラリはまず LD_LIBRARY_PATH に含まれたディレクトリから探し出され、見つからなかった場合はデフォルトの探索場所である /lib 及び /usr/lib 以下から探し出されます(実際には ld.so.cache ファイルなども探索に利用されます)。LD_LIBRARY_PATH は「パスリスト」形式の環境変数であり、環境変数 PATH などと同様に「:」で区切って複数のディレクトリを指定することが出来ます。
一方、Cygwin は共有ライブラリ(DLL)の探索に LD_LIBRARY_PATH を利用しません。Cygwin では、DLL は環境変数 PATH に含まれたディレクトリ、及びカレントディレクトリから探し出されます(これは Windows の仕様です)。
ライブラリの動的ロード
共有ライブラリを利用する際には、「動的ロード」という方法を採用することも出来ます。動的ロードとは、共有ライブラリ内に含まれている関数を実際に利用しなければならない局面に遭遇した時点で、初めて共有ライブラリをメモリ中にロードするという方法です。この方法を利用すれば、「必要となる DLL を発見することが出来なかった場合は、『DLL が存在しない』などの警告メッセージを表示してから安全にアプリケーションを終了させる」といった実装を行うことが可能になります。
UNIX 系 OS では、動的ロードには dlopen() という関数を利用します。dlopen() は LD_LIBRARY_PATH に含まれたディレクトリから共有ライブラリを探し出します(デフォルトである /lib 及び /usr/lib も探索対象となります)。
動的ロードは Windows でもサポートされており、そのためには LoadLibrary() という Win32 API を利用します。LoadLibrary() は Cygwin アプリケーションからも利用出来ますが、Cygwin DLL には dlopen() も実装されていますので、こちらを利用することも出来ます。
Cygwin DLL の dlopen() は UNIX 系 OS と同様、DLL の探索に LD_LIBRARY_PATH を利用します。しかし Cygwin では LD_LIBRARY_PATH はパスリスト形式の変数とは見なされないので、指定出来るディレクトリは一つだけに制限されています。
Cygwin におけるライブラリの動的ロード
Cygwin における DLL の動的ロードの例を示しましょう。
まず、次のような簡単なコード(dllsrc.c)を作成します。このコードには、「hello.」と出力するだけの単純な関数「hello」が実装されています。
#include <stdio.h>
void hello()
{
puts("hello.");
}
これを次のようにコンパイルして DLL(cygtest.dll) を作成し、適当なディレクトリに格納します。
$ pwd /tmp/dltest $ gcc -shared dllsrc.c -o cygtest.dll $ mkdir dll $ mv cygtest.dll dll
続いて、先ほど作成した DLL を動的にロードする実行形式を作成します。ソースコード(main.c)は次のようになります。
#include <stdio.h>
#include <dlfcn.h>
int main(void)
{
void *handle;
void (*func)();
/* cygtest.dll の動的ロード。拡張子「dll」はなくとも構わない。*/
handle = dlopen("cygtest", RTLD_LAZY);
if (!handle) {
fprintf (stderr, "%s\n", dlerror());
return 1;
}
/* dlopen() によって取得した cygtest.dll のハンドルから、
関数「hello」へのポインタを取得する。*/
func = dlsym(dlhandle, "hello");
if ((error = dlerror()) != NULL) {
fprintf (stderr, "%s\n", error);
return 1;
}
/* 関数「hello」を実行 */
func();
/* cygtest.dll のハンドルをクローズ。*/
dlclose(dlhandle);
return 0;
}
これをコンパイルして実行すると、次のようにエラーが発生します。
dlopen() によって動的にロードされる DLL(cygtest.dll)が見つけられなかったためです。
$ gcc main.c $ ./a.exe dlopen: Win32 error 126
次に、LD_LIBRARY_PATH を定義して実行してみます。
$ LD_LIBRARY_PATH=/tmp/dltest/dll ./a.exe hello.
今回は、DLL を動的にロードして関数を呼び出すことに成功しました。
なお、Linux や Solaris では、dlopen() の第二引数として
- RTLD_LAZY
- RTLD_NOW
- RTLD_GLOBAL
のいずれかの定数を指定するようになっていますが、Cygwin ではどれを定義しても同じ動作となります(実際には、どれを指定しても無視されます)。
Cygwin の dl 系関数には、もう一つ「dlfork()」という関数もあります。これは、動的ロードした DLL のハンドルを、fork() 時に子プロセスへと引き継ぐかどうかを指定するためのものです。