Program Library HOWTO David A. Wheeler 日本語訳 / 川崎貴彦 takahiko@hakubi.co.jp version 0.75, 31 October 2000 Revision History Revision 0.75-J1 4 Dec 2000 プログラマ用のこの HOWTO では、Linux 上でプログラムライブラリを作成、使 用する方法を論じます。ここには、静的ライブラリ、共有ライブラリ、動的に ロードされるライブラリ、が含まれます。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Table of Contents 1. はじめに 2. 静的ライブラリ 3. 共有ライブラリ 3.1. 約束ごと 3.2. ライブラリはどのように使われるか 3.3. 環境変数 3.4. 共有ライブラリの作成 3.5. 共有ライブラリのインストールと使用 3.6. 非互換ライブラリ 4. 動的にロードされる (Dynamically Loaded; DL) ライブラリ 4.1. dlopen() 4.2. dlerror() 4.3. dlsym() 4.4. dlclose() 4.5. DL ライブラリの例 5. 雑録 5.1. nm コマンド 5.2. 特別な関数 _init と _fini 5.3. 共有ライブラリはスクリプト化できる 5.4. GNU libtool 5.5. 極端に小さな実行可能ファイル 6. さらに多くの例 6.1. ファイル libhello.c 6.2. ファイル libhello.h 6.3. ファイル demo_use.c 6.4. ファイル script_static 6.5. ファイル script_shared 6.6. ファイル demo_dynamic.c 6.7. ファイル script_dynamic 7. その他の情報源 8. 著作権とライセンス 1. はじめに プログラマのためのこの HOWTO は、GNU ツールセットを使用している Linux 上でプログラムライブラリを作成、使用する方法を論じます。 ``プログラムラ イブラリ'' とは、単に、あとでプログラムに組み込まれることになるコンパイ ル済みのコード (及びデータ) を含むファイルのことです。プログラムライブ ラリは、プログラムを、よりモジュール化し、より速く再コンパイルでき、よ り簡単に更新できるものにします。プログラムライブラリは、三つのタイプ― ―静的ライブラリ、共有ライブラリ、動的にロードされる (dynamically loaded; DL) ライブラリ――に分類することができます。 この文書は、最初に、静的ライブラリ――プログラムが実行される前にその実 行可能プログラムに組み込まれるライブラリ――について論じます。それから 、共有ライブラリ――プログラム実行時にロードされ、かつ複数のプログラム 間で共有されるライブラリ――について論じます。最後に、動的にロードされ る (dynamically loaded; DL) ライブラリ――プログラム実行中の任意の時点 でロードして使用することが可能なライブラリ――について論じます。 DL ラ イブラリは、実際には異なるライブラリ形式というわけではありません (静的 ライブラリも共有ライブラリも DL ライブラリとして使用することが可能です) 。その代わりに、プログラマが DL ライブラリをどのように使用するかという 点において、違いがあります。HOWTO は、さらに多くの例を挙げている章、そ の他の情報源への参照を挙げている章、をもって終了します。 この HOWTO は実行可能ファイルとライブラリのための Executable and Linking Format (ELF) 形式――昨今のほとんど全ての Linux ディストリビュ ーションで使用されている形式――についてのみ論じます。 GNU gcc ツールセ ットは、実際には ELF 以外のライブラリ形式を扱うことができます。特に、ほ とんどの Linux ディストリビューションでは、旧式の a.out 形式を今なお使 用することが可能です。しかしながら、これらの形式はこの文書の対象外です 。 共有ライブラリを指して dynamically linked libraries (DLL) という用語を 使う人がいること、その DLL という用語を DL ライブラリとして使用される任 意のライブラリを意味するために使う人がいること、また、どちらかの条件を 満たすライブラリを意味するために DLL という用語を使う人がいること、には 注意したほうがよいです。いずれの意味を取り上げるにしても、この HOWTO は Linux 上でのこれら全ての DLL についてカバーします。 多くのシステムに移植されるアプリケーションを作成しているならば、ライブ ラリを構築しインストールするのに、Linux ツールを直接使用する代わりに GNU libtool を使用することを考慮したほうがよいかもしれません。GNU libtool は、共有ライブラリ使用の複雑さ (例えば、それらを作成しインスト ールするなど) を一貫性のあるポータブルなインターフェースで隠す、汎用的 なライブラリサポートスクリプトです。Linux 上では、GNU libtool はこの HOWTO に記述されているツールと慣習の上に構築されています。動的にロード されるライブラリへのポータブルなインターフェース用に、様々なポータビリ ティラッパーを使用することができます。GNU libtool は、 ``libltdl'' と呼 ばれるその種のラッパーを含んでいます。他の選択肢としては、可搬性のある 方法で動的ローディングをサポートする glib ライブラリ (glibc と混同しな いでください) を使用することもできます。glib については、 http:// developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html でさらに知ることができます。再度述べますが、Linux 上では、この機能は、 この HOWTO 内に記述されている構成物を使用することによって実装されていま す。もしもあなたが実際に Linux 上でコードを開発、もしくはデバッグしてい るならば、おそらくなおさらのこと、この HOWTO 内の情報を欲されることでし ょう。 この HOWTO の一次配布場所は http://www.dwheeler.com/program-library で あり、Linux Documentation Project (http://www.linuxdoc.org) に寄贈され ています。著作権は David A. Wheeler にあり (Copyright (C) 2000)、 General Public License (GPL) でライセンスされています。さらなる情報につ いては最後の章を読んでください。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2. 静的ライブラリ 静的ライブラリは、通常のオブジェクトファイルの単なる集合体です。慣習的 に、静的ライブラリは ``.a'' という拡張子を持ちます。この集合体は、ar (archiver) プログラムを使用して作成されます。静的ライブラリは以前ほどに は使われなくなっていますが、それは、共有ライブラリのほうが優れているこ とによります (あとで述べます)。それでもまだ、静的ライブラリは時々作成さ れ――はじめは歴史的な理由で存在していたのですが――、説明するのもより 簡単です。 ユーザは、コードを再コンパイルする必要もなく静的ライブラリをプログラム にリンクすることができ、再コンパイルにかかる時間を節約できます。注―― 昨今のより高速なコンパイラのことを考えれば、再コンパイル時間は重要では なくなってきています――そのためにこの理由付けは以前ほど有力ではありま せん。静的ライブラリは、その開発者が、ライブラリへリンクすることをプロ グラマに許可はしたいがライブラリソースコードは渡したくはない、という場 合にしばしば役に立ちます (これはライブラリベンダーにとっては好都合です が、そのライブラリを使おうとしているプログラマにとっては明らかに好都合 とは言えません)。論理的には、実行可能ファイルにリンクされる静的 ELF ラ イブラリ内のコードは若干速く (1-5%) 動作するはずですが、実際には、他の ごちゃごちゃした要因のため、その通りになることは稀のようです。 静的ライブラリを作成する、もしくは既に存在する静的ライブラリにさらにオ ブジェクトファイルを追加するには、次のようなコマンドを使用してください ―― ar rcs my_library.a file1.o file2.o このコマンド例では、静的ライブラリ my_library.a にオブジェクトファイル file1.o と file2.o を付け加えています。まだ my_library.a が存在していな ければ、作成します。静的ライブラリ作成に関してさらに情報を得るには、ar (1) を参照してください。 一度静的ライブラリを作成してしまうと、それを使いたくなることでしょう。 実行可能プログラムを作成するときにコンパイルとリンク処理の一部として呼 び込むことで、共有ライブラリを使用できます。実行可能ファイルを作成する のに gcc(1) を使っているならば、ライブラリを指定するのに -l オプション を使用できます。より詳しい情報については info:gcc を参照してください。 -l と -L オプションを使って、リンカ ld(1) を直接使用することもできます 。しかしながら、ld(1) のインターフェースは gcc(1) よりも変更されやすい ので、ほとんどの場合は gcc(1) を使うほうがよいです。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3. 共有ライブラリ 共有ライブラリは、プログラムが起動するときにロードされるライブラリです 。共有ライブラリが適切にインストールされると、その後に起動される全ての プログラムは、自動的にその新しい共有ライブラリを使うことになります。実 際には、これ以上にはるかに柔軟で洗練されています。というのは、 Linux に よるアプローチは次のことを可能にするからです―― ・ ライブラリを更新しながらも、そのライブラリの古くて後方互換性のな いバージョンを使いたいというプログラムを、依然としてサポートできる ・ 特別なプログラムを実行するとき、特定のライブラリ、もしくはライブ ラリ内の特定の関数でさえオーバーライドできる ・ 既に存在しているライブラリを使用してプログラムが動いている間にも 、この全てをおこなうことができる ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.1. 約束ごと これらの望ましい特性すべてを共有ライブラリがサポートするためには、多く の慣習と指針に従わなければなりません。ライブラリの名前、特に ``soname'' と ``real name'' の違いについて (及びそれらがどのように相互作用するかに ついて) 理解する必要があります。また、それらがファイルシステム内のどの 場所に置かれるべきであるかについても、理解する必要があります。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.1.1. 共有ライブラリ名 全ての共有ライブラリは、``soname'' と呼ばれる特別な名前を持っています。 soname は ``lib'' というプリフィックス、ライブラリの名前、``.so'' とい う句を持ち、ピリオドと、インターフェースが変更されるときには必ず増やさ れるバージョン番号が後に続きます (特別な例外として、最低レベルの C ライ ブラリは ``lib'' では始まりません) 。完全に記述された soname は、そのラ イブラリ自身が含まれるディレクトリをプリフィックスとして含んでいます。 実際のシステムでは、完全に記述された soname は、共有ライブラリの ``real name'' への単なるシンボリックリンクになっています。 全ての共有ライブラリは、``real name'' ――実際のライブラリコードを含む ファイル名――も持っています。real name は、 soname に、ピリオド、マイ ナー番号、もう一つピリオド、リリース番号、を加えたものです。最後のピリ オドとリリース番号はなくてもかまいません。マイナー番号とリリース番号は 、どのバージョンのライブラリがインストールされているかを正確に示し、設 定管理の助けとなります。これらの番号は、――そのようにすれば物事をより 単純化できるにもかかわらず――ドキュメントの中でライブラリを説明するの に用いられている番号と同じではないかもしれない、ということに注意してく ださい。 加えて、ライブラリ要求時にコンパイラが使用する名前というものもあります (``linker name'' と呼ぼうと思います) 。それは、単に、一切のバージョン番 号を取り除いた soname です。 共有ライブラリを管理する鍵となるのは、これらの名前の使い分けです。必要 となる共有ライブラリの一覧表を内部に作成するときには、プログラムは、必 要となる soname をリストアップするのみとします。逆に、共有ライブラリを 作成するときには、(より詳細なバージョン情報を持つ) 特定のファイル名でラ イブラリを作成するのみとします。新しいバージョンのライブラリをインスト ールするときには、二、三の特別なディレクトリのうちの一つにそれをインス トールし、それから ldconfig(8) プログラムを実行します。ldconfig は、既 に存在するファイルを調べ、real name へのシンボリックリンクとして、 soname を作成します。同様にして、キャッシュファイル /etc/ld.so.cache も 設定します (すぐに説明します)。 ldconfig は linker name を設定しません。典型的には、この設定はライブラ リインストール時におこなわれ、``最新の'' soname もしくは最新の realname への単なるシンボリックリンクとして、linker name が作成されます。ほとん どの場合において、ライブラリを更新したら、リンク時にそれを自動的に使用 したいと思うでしょうから、soname へのシンボリックリンクとして linker name を作っておくことをお勧めします。私は、なぜ ldconfig は自動的に linker name を設定しないのかを、H. J. Lu に尋ねました。彼の説明は、基本 的には、「ライブラリの最新バージョンを使ってコードを実行したいと思われ るかも知れませんが、そうではなく、(おそらく互換性のない) 古いライブラリ にリンクする開発を望んでいるということもありうるのです」、というもので した。そのため、ldconfig は、あなたがどのライブラリにプログラムをリンク させたいのかということについては、何の仮定もおこないません。ですので、 リンカがライブラリに使うものを更新するためには、インストーラがシンボリ ックリンクを明確に変更しなければならないのです。 例えば、/usr/lib/libreadline.so.3 は完全に記述された soname であり、 ldconfig が /usr/lib/libreadline.so.3.0 というような何らかの real name に対するシンボリックリンクとして設定するものです。 /usr/lib/ libreadline.so という linker name も存在するべきで、それは、 /usr/lib/ libreadline.so.3 を参照するシンボリックリンクになることでしょう。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.1.2. ファイルシステム配置 共有ライブラリはファイルシステムのどこかに配置されなければなりません。 ほとんどのオープンソースソフトウェアは、GNU 規準に従う傾向があります― ―詳細は info:standards#Directory_Variables にある info ファイルドキュ メントを見てください。 GNU 規準は、ソースコードを配布するとき、デフォル トでは全てのライブラリを /usr/local/lib にインストールすることを推奨し ています (全てのコマンドが /usr/local/bin に入るべきだとも勧めています) 。また、これらのデフォルトをオーバーライドしたり、インストールルーチン を呼び出したりするための慣習をはっきり述べています。 ファイルシステム階層規準 (Filesystem Hierarchy Starndard; FHS) は、ディ ストリビューションにおいて何がどこにインストールされるべきかを論じてい ます (http://www.pathname.com/fhs を見てください) 。 FHS に従えば、ほと んどのライブラリは /usr/lib にインストールされるべきですが、起動に必要 とされるライブラリは /lib に、そしてシステムの一部になってはいないライ ブラリは /usr/local/lib にインストールされるべきです。 実際には、これら二つの文書間に矛盾はありません。GNU 規準は、ソースコー ド開発者のためのデフォルトを推奨しているのであり、一方で FHS は、ディス トリビュータ (通常、システムパッケージ管理システムを通してソースコード のデフォルトを選択的にオーバーライドする人々) のためのデフォルトを推奨 しているのです。実際にこれはうまく機能しています。あなたがダウンロード した ``最新の'' (おそらくバグだらけの!) ソースコードは、自動的に自分自 身を ``ローカルな'' ディレクトリ (/usr/local/) にインストールします。そ してコードが成熟してきたら、パッケージ管理ツールは、ディストリビューシ ョン用の標準的な位置にコードを配置するためにデフォルトを単にオーバーラ イドできます。あなたのライブラリが、ライブラリ経由でしか呼び出されるこ とのないプログラムを呼び出しているのならば、それらのプログラムを /usr/ local/libexec (あるディストリビューションでは /usr/libexec になります) に配置するべきです。一つ事態を複雑にしていることがあって、それは、 Red Hat から派生したシステムがデフォルトでは /usr/local/lib をライブラリ検 索対象に含めていないということです。 /etc/ld.so.conf に関する下記の議論 を見てください。他の標準的なライブラリロケーションとしては、X Window System 用の /usr/X11R6/lib が含まれます。 /lib/security は PAM モジュー ル用に使われますが、それらは通常 DL ライブラリ (これもあとで説明します) としてロードされる、ということに注意してください。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.2. ライブラリはどのように使われるか GNU glibc ベースのシステム――全ての Linux システムを含みます――では、 ELF バイナリ実行ファイルを起動させると、自動的にプログラムローダがロー ドされ、実行されます。 Linux システム上では、このローダは /lib/ ld-linux.so.X (X にはバージョン番号が入ります) という名前です。このロー ダは、プログラムによって使用されるその他の全ての共有ライブラリを順次探 し出し、ロードします。 検索対象となるディレクトリのリストは、/etc/ld.so.conf ファイル内に書か れています。Red Hat から派生しているディストリビューションの多くは、通 常 /etc/ld.so.conf ファイル内に /usr/local/lib を含めていません。私はこ れをバグだと考えており、また、/usr/local/lib を /etc/ld.so.conf に追加 することは、 Red Hat から派生しているシステム上で多くのプログラムを走ら せるために必要とされる、共通の ``修正'' だと思っています。 ライブラリ内の幾つかの関数をオーバーライドしたいだけで、残りはそのまま にしておきたいならば、オーバーライドするライブラリ (.o ファイル) の名前 を /etc/ld.so.preload に入れることができます。これらの ``先行してロード する'' ライブラリは、標準セットに先行します。この先行してロードするファ イルは、典型的には緊急用のパッチとして使われます。ディストリビューショ ンは、通常、配布される際にこのようなファイルを含むことはないでしょう。 プログラム起動時にこれら全てのディレクトリを検索するのは、とても非効率 的なので、実際にはキャッシュ配置が使われます。 ldconfig(8) プログラムは デフォルトで /etc/ld.so.conf ファイルを読み込み、適切なシンボリックリン クを動的リンクディレクトリ内に設定します (そのため、標準の慣習に沿うこ とになります) 。それから、キャッシュを /etc/ld.so.cache ――あとで他の プログラムに使われます――に書き込みます。これは、ライブラリへのアクセ スを非常に速くします。暗黙的に言えることは、DLL が追加されたときは必ず 、もしくは、DLL が削除されたり、 DLL ディレクトリのセットが変化したとき には、ldconfig が実行されなければならない、ということです。ldconfig の 実行は、ライブラリインストール時にパッケージ管理ツールによっておこなわ れるステップの一つであることが多いです。それ以降、起動時には、動的ロー ダは実際に /etc/ld.so.cache ファイルを使い、必要とするライブラリをロー ドします。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.3. 環境変数 様々な環境変数がこの処理手順を制御できます。事実、この処理手順をオーバ ーライドするのに使える環境変数が存在します。例えば、この特殊な実行を、 一時的に他のライブラリで代替することができます。 Linux では、環境変数 LD_LIBRARY_PATH は、標準的なディレクトリ群に先立ってライブラリが検索さ れるべきディレクトリ群を、コロンで区切って並べたものです。これは、新し いライブラリをデバッグしているときや、特別な目的のために非標準的なライ ブラリを使用しているときに便利です。環境変数 LD_PRELOAD は、標準セット をオーバーライドするオブジェクトファイルを関数と共に、ちょうど /etc/ ld.so.preload でおこなわれているように、列挙します。これらの機能は、/ lib/ld-linux.so ローダにより実装されています。 LD_LIBRARY_PATH は多くの Unix ライクなシステム上で機能しますが、全てのシステム上で動くわけではな いことに注意しましょう。例えば、HP-UX でも同じ機能が利用できますが、そ れは SHLIB_PATH としてですし、AIX では LIBPATH を通じてということになり ます。また、LD_LIBRARY_PATH は開発やテストには便利ではありますが、通常 使用のためにインストール時に修正されるべきではありません。この理由の説 明については、 http://www.visi.com/~barr/ldpath.html の ``なぜ LD_LIBRARY_PATH はいけないのか'' を参照してください。 実際には、ローディング処理手順を制御する環境変数はほかにもたくさん存在 します。それらの名前は、LD_ や RTLD_ ではじまります。それらのほとんどは 、ローダ処理の低レベルなデバッグや、特殊化されたケイパビリティを実装す るためのものです。ほとんどは、十分にドキュメント化されていません。それ らについて知る必要があるならば、学習するもっともよい方法は、ソースコー ドを読むことです。 特別な対処がなされないならば、動的にリンクされるライブラリに対する制御 をユーザに認めるということは、setuid/setgid プログラムにとっては悲惨な ものになるでしょう。そのため、GNU ローダでは、プログラムが setuid もし くは setgid されている場合、これらの変数 (及び類似の変数) は無視される か、もしくは、それらができることは大幅に制限されます。ローダは、プログ ラムの信任証 (credential) を調べることによって、そのプログラムが setuid もしくは setgid されているかどうかを確認します。もしも uid と euid が異 なるか、もしくは gid と egid が異なるなら、ローダはそのプログラムが setuid/setgid されている (もしくはそのようなプログラムから実行された) と推定し、リンク処理をコントロールする能力を大幅に制限します。 GNU glibc ライブラリのソースコードを読めば、これについて見ることができるで しょう。特に、elf/rtld.c ファイルと sysdeps/generic/dl-sysdep.c ファイ ルを見てください。これは、uid と gid を euid と egid に等しくしてからプ ログラムを呼べばこれらの変数が完全に機能する、ということを意味していま す。その他の Unix ライクなシステムは、この状況を異なる方法で扱いますが 、同じ理由――setuid/setgid プログラムは環境変数群によって過度に影響を 受けるべきではないという理由――によります。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.4. 共有ライブラリの作成 共有ライブラリの作成は簡単です。まずはじめに、gcc の -fPIC フラグ (これ は、共有ライブラリに必要とされる ``位置独立コード (position independent code)'' の生成を可能にします) を使って、共有ライブラリに組み込まれるこ とになるオブジェクトファイルを作成します。それから、次の形式を使って共 有ライブラリを作成してください―― gcc -shared -Wl,-soname,your_soname \ -o library_name file_list library_list 二つのオブジェクトファイル (a.o と b.o) を作成し、これら両方を含む共有 ライブラリを作成する例をここに挙げます。コンパイル処理が、デバッグ情報 (-g) を含み、警告メッセージを生成する (-Wall) ということ――これらは共 有ライブラリに必要とはされませんが推奨されます――には注意してください 。コンパイル処理は (-c を使って) オブジェクトファイルを生成し、そして、 要求される -fPIC オプションをはっきりと含むことになります。 gcc -fPIC -g -c -Wall a.c gcc -fPIC -g -c -Wall b.c gcc -shared -Wl,-soname,libmystuff.so.1 \ -o libmystuff.so.1.0.1 a.o b.o -lc 注意すべきことが幾つかあります―― ・ 生成されたライブラリを strip しないでください。また本当に必要でな い限りコンパイラオプション -fomit-frame-pointer を使わないでくださ い。生成されたライブラリは機能するでしょうが、これらの作業は、デバ ッガをほとんど使い物にならなくしてしまいます。 ・ コードを生成するには、-fpic ではなく、-fPIC を使ってください (前 者は機能しないこともあります。というのは、分岐が大規模な配置転換を 必要とする場合、-fpic は完全な位置独立コードを生成しないかもしれな いからです)。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.5. 共有ライブラリのインストールと使用 一度共有ライブラリを作成してしまうと、それをインストールしたくなること でしょう。簡単な方法は、標準的なディレクトリ (例えば /usr/lib など) の 一つに、そのライブラリをコピーし、ldconfig(8) を実行することです。 もしもこれができないならば (例えば、あなたが /usr/lib を変更する権利を 持っていないなど)、事態を制御するために環境変数を使用することができます 。まずはじめに、どこかに共有ライブラリを作成する必要があるでしょう。そ れから、必要なシンボリックリンク、特に soname から real name へのシンボ リックリンク (同様に、バージョン番号をまったく指定しないユーザのために 、バージョン番号のない soname 、つまり、``.so'' で終わる soname からの シンボリックリンクも) 設定する必要があるでしょう。もっとも簡単な方法は 、次のコマンドラインを実行することです―― ldconfig -n directory_with_shared_libraries それから、LD_LIBRARY_PATH ――いつもの場所よりも先に共有ライブラリの検 索対象となるディレクトリのリストをコロンで区切ったもの――を設定します 。bash をお使いでしたら、次の方法で my_program を実行できます。 LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH my_program 幾つかの選択された関数をオーバーライドしたいだけならば、オーバーライド するオブジェクトファイルを作成して LD_PRELOAD を設定するだけで可能です 。このオブジェクトファイル内の関数は、対象となっている関数だけをオーバ ーライドします。 普段は、心配する必要もなくライブラリを更新できます。もしも API の変更が あるならば、ライブラリ作成者は soname を変更するでしょう。しかしながら 、もしも同じ soname のままのライブラリに対する更新個所において、あるプ ログラムが中断してしまうようなら、古いライブラリをどこかにコピーし、そ のプログラムの名前を変更することによって (古い名前に ``.orig'' を付け足 すなど) 、強制的に古いライブラリバージョンを使うことができます。使用す るライブラリを再設定し、実際に実行する (名前を変更された) プログラムを 呼び出すための小さな ``ラッパー'' スクリプトも作成してください。番号付 けの慣習は、同一ディレクトリ内に複数のバージョンが存在することを可能に していますが、お望みなら、古いライブラリをそれ独自の特別な場所に置くこ ともできます。ラッパースクリプトは、次のようなものになるでしょう―― #!/bin/sh export LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH exec /usr/bin/my_program.orig $* ldd(1) を使えば、あるプログラムによって使用されている共有ライブラリのリ ストを調べることができます。例えば、次のようにタイプすれば、 ls によっ て使用されている共有ライブラリを確認することができます―― ldd /bin/ls 一般的には、プログラムの依存する soname のリストが、名前解決後のディレ クトリ名と共に得られます。実際には全ての場合において、少なくとも二つの 依存要素を見ることになるでしょう―― ・ /lib/ld-linux.so.N (N は、1 かそれ以上。たいていは少なくとも 2) 。これは、他の全てのライブラリをロードするためのライブラリです。 ・ libc.so.N (N は、6 かそれ以上) 。これは C ライブラリです。他の言 語でさえ、C ライブラリを使用する傾向があります (少なくともそれら自 身のライブラリを実装するために) 。そのため、ほとんどのプログラムは 少なくともこのライブラリは含んでいます。 注意――信用できないプログラムに対して ldd を実行してはいけません。これ については、 ldd(1) のマニュアルで明確に述べられています。 ldd は、当該 プログラムを直接呼び出すことで機能しています。信用できないプログラムが 予期していないコードを実行してしまうことがありえます。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.6. 非互換ライブラリ 新しいバージョンのライブラリが古いものとバイナリ非互換であるときは、 soname を変更する必要があります。C においては、ライブラリがバイナリ互換 ではなくなってしまう四つの基本的な理由があります。 1. 元の仕様に適合しないように関数の動作が変更されてしまう 2. エクスポートされるデータ項目が変更されてしまう (例外――構造体がライ ブラリ内でのみアロケートされる場合に限り、構造体の末尾に任意の項目 を追加するのは問題ない) 3. エクスポートされている関数が削除されてしまう 4. エクスポートされている関数のインターフェースが変更されてしまう これらの理由を回避できるならば、ライブラリをバイナリ互換に保つことがで きます。別の言い方をすると、これらの変更を避ければ、Application Binary Interface (ABI) 互換を保つことができる、ということです。例えば、新しい 関数を追加したいけれども古い関数を削除したくはないかもしれません。構造 体の末尾にのみアイテムを追加し、ライブラリにだけその構造体をアロケート することを許可し (アプリケーションには許可しない)、その追加のアイテムを オプション扱いにする (もしくはライブラリがそれらを満たすようにする)、な どの操作をおこなって生じた変更が、古いプログラムに対して影響を与えない ことを確認できる場合にのみ、アイテムを追加することができます。気を付け てください。もしもユーザが構造体を配列で使っているならば、その構造体は 拡張できません。 C++ (および、コンパイル済み組込みテンプレート且つ/又はコンパイル済みの ディスパッチされるメソッドをサポートするその他の言語) では、状況はより 複雑になります。上記の問題点全てが適用される上、さらに多くの問題があり ます。理由は、幾つかの情報がコンパイルされたコード内に ``隠された状態で '' 実装されているということにあるのですが、このことが、 C++ が一般的に どのように実装されているかを知らない人にはよく分からないような依存問題 を引き起こしてしまうのです。正確に言えば、それらは ``新しい'' 問題では ありません。単に、コンパイル済みの C++ のコードが、あなたを驚かせること になるかもしれない方法でそれらの問題を引き起こすということなのです。次 のものは、バイナリ互換を維持するために C++ 内でやってはいけない項目のリ スト (おそらく不完全ですが) であり、 Troll Tech テクニカル FAQ により報 告されているものです。 1. メンバ関数を再実装したものを追加する (古いバイナリが元の実装を呼び出 すのが安全ではない場合)。というのは、コンパイラは SuperClass:: virtualFunction() 呼出しをコンパイル時に評価するからです (リンク時 ではありません)。 2. 仮想メンバ関数を追加または削除する。というのは、この作業は全サブクラ スの仮想関数テーブルのサイズと配置を変更するだろうからです。 3. インラインメンバ関数経由でアクセスされうるデータメンバのタイプを変更 したり、またはそれらを移動させたりする。 4. クラス階層を変更する。ただし、リーフ (訳注:下位クラスを持たないクラ ス) の新規追加を除く。 5. private データメンバを追加または削除する。というのは、この作業は全サ ブクラスのサイズと配置を変更するだろうからです。 6. public もしくは protected メンバ関数がインライン関数でない場合に、そ れらを削除する。 7. public もしくは protected メンバ関数をインライン化する。 8. インライン関数がおこなっていたことを変更する。ただし、古いバージョン が動作し続けている場合を除く。 9. ポータブルなプログラムのメンバ関数のアクセス権 (すなわち、 public, protected または private) を変更する。というのは、アクセス権を関数 名に押し込んでしまうコンパイラも存在するからです。 C++ ライブラリの開発者は、この長ったらしいリストを手にして、バイナリ互 換性をなくすことになってしまう時折の更新作業以上のことを特に考慮しなけ ればなりません。幸いにして、Unix ライクなシステム (Linux を含みます) で は一つのライブラリの複数のバージョンを同時にロードすることができるので 、ディスクスペースを失うことにはなりますが、ユーザは古いライブラリを必 要とする ``古い'' プログラムをその後も実行することが可能ではあります。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4. 動的にロードされる (Dynamically Loaded; DL) ライブラリ 動的にロードされる (dynamically loaded; DL) ライブラリは、プログラムの 起動時以外のときにロードされるライブラリです。これはプラグインやモジュ ールを実装するのに特に役に立ちます。というのは、プラグインが必要になる まで、それをロードするのを待つことができるからです。例えば、 Pluggable Authentication Modules (PAM) システムは、管理者が認証の設定や再設定をお こなえるようにするため、 DL ライブラリを使用しています。また、全体を止 めることなく、効率を上げる目的で、その時々でコードをマシンコードにコン パイルし、そのコンパイル後のものを使用するというインタプリタを実装する のにも役に立ちます。この方法は、ジャストインタイムコンパイラや、マルチ ユーザダンジョン (multi-user dungeon; MUD) の実装時にも役に立ちます。 Linux では、実際のところ、DL ライブラリは形式という点においては特別では ありません。それらは、標準的なオブジェクトファイル、もしくは今までに述 べたような標準的な共有ライブラリとして構築されています。主な違いは、ラ イブラリが、プログラムのリンク時や起動時に自動的にはロードされない、と いう点です。その代わり、ライブラリをオープンし、シンボルを検索し、エラ ーを処理し、ライブラリを閉じる、という API は存在します。この API を使 うためには、 C ユーザはヘッダファイル をインクルードする必要 があります。 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 でサポートされるインターフェースを直接使うことに依然として興味 をお持ちならば、読み進んでください。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.1. dlopen() dlopen(3) 関数は、ライブラリをオープンし、使用するための準備をします。 C では、そのプロトタイプは次のようになります―― void * dlopen(const char *filename, int flag); ファイル名が ``/'' ではじまるならば (つまり絶対パスならば) 、 dlopen() はライブラリを検索しません。そうでないならば、dlopen() は次の順序でライ ブラリを検索します―― 1. ユーザの LD_LIBRARY_PATH 環境変数内のコロンで区切られたディレクトリ リスト 2. /etc/ld.so.cache に指定されたライブラリリスト 3. /usr/lib, 次が /lib dlopen() では、flag の値は、RTLD_LAZY ――``動的ライブラリのコードが実 行されるときに、未定義シンボルを解決せよ'' という意味です――、もしくは 、RTLD_NOW ――``dlopen() がリターンする前に全ての未定義シンボルを解決 せよ、それができないようならば失敗せよ'' という意味です――、のどちらか でなければいけません。RTLD_GLOBAL は、 flag のどちらかの値と任意に論理 和結合されるもので、続けてライブラリをロードすることによりライブラリ内 で定義されている外部シンボルを得られる、ということを意味しています。デ バッグ中は、おそらく RTLD_NOW を使いたくなるでしょう。RTLD_LAZY を使う と、解決されない参照があったときに不可解なエラーが生成されます。 RTLD_NOW を使うと、ライブラリのオープンには若干時間が多くかかるようにな ります (しかし、のちのちの検索スピードは速くなります) 。このことがユー ザインターフェースの問題になるようでしたら、あとで RTLD_LAZY にかえるこ とができます。 ライブラリがお互いに依存しているようなら (例えば、X が Y に依存してい る) 、依存されているほうを先にロードしてください (この例で言えば、Y を 先にロードし、それから X をロードします) 。 dlopen() の戻り値は、他の DL ライブラリルーチンで使用される ``ハンドル '' ――その実体は隠蔽されるぺきものと考えられている――です。ロードの試 みが成功しない場合、dlopen() は NULL を返しますので、この値をチェックす る必要があります。同じライブラリが dlopen() で二回以上ロードされると、 同じファイルハンドルが返されます。 もしもライブラリが _init という名前のルーチンをエクスポートしていれば、 そのコードは dlopen() が戻る前に実行されます。あなたのライブラリでも、 初期化ルーチンを実装するためにこれを使うことができます。詳細は Section 5.2 を参照してください。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.2. dlerror() dlerror() を呼べば、エラーを報告できます。dlerror() は、 dlopen(), dlsym() もしくは dlclose() の最後の呼出しによるエラーについて記述してあ る文字列を返します。一つ変わっているのは、dlerror() を呼び出すと、以降 の dlerror() の呼出しは、ほかのエラーが発生するまで NULL を返すという点 です。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.3. dlsym() DL ライブラリが使えなければ、それをロードしても意味がありません。 DL ラ イブラリを使うための主となるルーチンは、dlsym(3) です。これは、与えられ た (オープン済みの) ライブラリ内にあるシンボルの値を検索するものです。 この関数は次のように定義されます―― void * dlsym(void *handle, char *symbol); handle は dlopen で返された値で、symbol はヌル文字で終端された文字列で す。回避可能ならば、dlsym() の結果を void* ポインタに格納しないでくださ い。というのは、それを利用するたびにキャストしなければいけなくなるから です (プログラムをメンテナンスしようとしている人たちに、より少ない情報 しか与えないことにもなります) 。 dlsym() は、シンボルが見つからなければ NULL という結果を返します。シン ボルが NULL もしくはゼロという値をとることはありえないと分かっていれば 、それで構いません。しかし、そうでない場合は潜在的に曖昧さが残ります。 もしも NULL を受け取った場合、それは、そんなシンボルは存在しないという ことを意味するのでしょうか、もしくはそのシンボルの値が NULL であること を意味するのでしょうか? 標準的な解答は、dlerror() をはじめに呼び (存在 しているかもしれないエラー条件をクリアするためです)、それからシンボルを 要求するために dlsym() を呼び、エラーが発生しているかどうかを調べるため に再度 dlerror() を呼び出すことです。コードの断片は次のようになるでしょ う―― dlerror(); /* エラーをクリアする */ s = (actual_type) dlsym(handle, symbol_being_searched_for); if ((err = dlerror()) != NULL) { /* ハンドルエラー。シンボルを見つけられなかった */ } else { /* シンボルが見つかった。その値は s に格納されている */ } ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.4. dlclose() dlopen() の逆が dlclose() で、これは DL ライブラリをクローズします。 dl ライブラリは動的なファイルハンドルへのリンク数を管理しているので、同一 動的ライブラリに対して、dlopen が成功した回数と同じ数の dlclose が呼ば れない限り、当該ライブラリは実際にはメモリ上から削除されません。そのた め、同じプログラムが同じライブラリを何回ロードしても、問題にはなりませ ん。ライブラリの割当てが解除される場合は、(もしも存在するならば) _fini 関数が呼ばれます。詳細は Section 5.2 を参照してください。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.5. DL ライブラリの例 dlopen(3) の man ページからの例をここに載せます。この例は、数学ライブラ リをロードし、2.0 のコサインを出力し、また、全てのステップでエラーをチ ェックしています (推奨されています) ―― #include #include 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'' フラ グは必ずしも機能しない、ということには注意しておいてください。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5. 雑録 5.1. nm コマンド nm(1) コマンドは、与えられたライブラリ内のシンボルのリストを報告します 。静的ライブラリ、共有ライブラリのどちらに対しても機能します。 nm(1) は 与えられたライブラリで定義されているシンボル名、シンボルの値、シンボル のタイプを表示します。また、そのライブラリ内に情報が存在するならば (-l オプションを見てください) 、シンボルがソースコード内のどこで (ファイル 名と行番号) 定義されているかということも特定できます。 シンボルタイプについてはもう少し説明が必要です。タイプは一文字で表示さ れます。小文字はそのシンボルがローカルであることを意味し、大文字はその シンボルがグローバル (外部) であることを意味します。典型的なシンボルの タイプは次のものを含みます―― T (コードセクション内の普通の定義) D (初 期化されたデータセクション) B (初期化されていないデータセクション) U (未定義。シンボルはライブラリによって使われているが、ライブラリ内では定 義されていない) W (weak. もしも他のライブラリもこのシンボルを定義してい た場合、その定義がオーバーライドする) 関数の名前は覚えているけれども、それがどのライブラリで定義されているか 正確には思い出せない場合、ライブラリ名を見つけるため、nm の ``-o'' オプ ション (各ラインのファイル名の前に置きます) に grep を続けて使うことが できます。 Bourne シェルであれば、/lib, /usr/lib, /usr/lib の直下のサブ ディレクトリ、および /usr/local/lib 内の全ライブラリを対象にして ``cos'' を検索するには、次のようにします―― nm -o /lib/* /usr/lib/* /usr/lib/*/* \ /usr/local/lib/* 2> /dev/null | grep 'cos$' nm に関するもっと多くの情報は、 info:binutils#nm にローカルにインストー ルされている nm の ``info'' ドキュメントで得られます。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.2. 特別な関数 _init と _fini 二つの特別な関数 _init と _fini は、モジュールの初期処理と終了処理を支 援します。もしもライブラリ内で関数 ``_init'' がエクスポートされていると 、そのライブラリのオープン時に dlopen() が呼ばれるたび、その関数が呼び 出されます。 C のプログラムでは、_init という名前の関数を定義することを 意味します。これに対応する _fini と呼ばれる関数も存在し、これは、クライ アントがライブラリの解放時に dlclose() を呼ぶたびに、呼び出されます (そ して解放されます) 。これらの関数の C プロトタイプは次のようになっていま す。 void _init(void); void _fini(void); Gcc でファイルを ``.o'' ファイルへとコンパイルするときは、忘れずに ``-nostartfiles'' オプションを付けてください。このオプションは、C コン パイラが .so ファイルに対してシステムスタートアップライブラリをリンクし ないようにします。このオプションを付けないと、``multiple-definition'' (重複定義) エラーになってしまいます。 _init と _fini に関する議論を加え ることを提案してくれたこと、およびその作成を手伝ってくれたことに対して 、 Jim Mischel と Tim Gentry に感謝します。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.3. 共有ライブラリはスクリプト化できる 通常のライブラリ形式の代わりに、特殊なスクリプト言語を使っているテキス トファイルを共有ライブラリとして GNU ローダが認めることは、注目に値しま す。これは、他のライブラリを間接的に結合させるのに役立ちます。例えば、 私の持つある一つのシステム上の /usr/lib/libc.so をリスティングしたもの は次のようになります。 /* GNU ld スクリプト 共有ライブラリを使うが、幾つかの関数は静的ライブラリ内にしか 存在しない。そのため、二番目に試みる。 */ GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a ) これに関するより詳しい情報は、ld リンカスクリプト (ld コマンド言語) に ついての texinfo ドキュメントを参照してください。一般的な情報は、info: ld#Options と info:ld#Commands にあり、よく使うコマンドは info:ld# Option Commands で説明されています。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.4. GNU libtool 多くのシステムに移植されるアプリケーションを作成しているならば、ライブ ラリを構築しインストールするのに、 GNU libtool を使用することを考慮した ほうがよいかもしれません。 GNU libtool は、汎用的なライブラリサポートス クリプトです。 Libtool は、共有ライブラリ使用の複雑さを一貫性のあるポー タブルなインターフェースで隠します。Libtool は、オブジェクト作成、ライ ブラリのリンク (静的および共有) 、実行可能ファイルのリンク、実行可能フ ァイルのデバッグ、ライブラリのインストール、実行可能ファイルのインスト ール、についてポータブルなインターフェースを提供します。また、プログラ ムを動的にロードするためのポータビリティラッパーである libltdl も含んで います。より詳細な情報は、 http://www.gnu.org/software/libtool/ manual.html を参照してください。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.5. 極端に小さな実行可能ファイル 「本当に小さな Linux 用 ELF 実行可能ファイル作成に関する慌ただしいチュ ートリアル」という文書が、よい参考となるでしょう。これは、本当に小さな 実行可能プログラムを作成する方法について論じています。率直に言えば、一 般的な環境では、これらのトリックのほとんどは使うべきではありませんが、 それらは、ELF が実際にどのように機能するかを示しているという点において 、極めて有益です。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6. さらに多くの例 下記に、全三つのアプローチ (静的、共有、および動的にロードされるライブ ラリ) の例をさらに挙げます。ファイル libhello.c は平凡なライブラリで、 ヘッダファイルとして libhello.h を持ちます。ファイル demo_use.c は、そ のライブラリの平凡な呼出しです。静的ライブラリや動的ライブラリとして当 該ライブラリを使う方法を示すため、コメント付きのスクリプト (script_static と script_dynamic) があとに続きます。さらに demo_dynamic.c と script_dynamic があとに続きます。これらは共有ライブラ リを動的にロードされるライブラリとして使う方法を示します。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.1. ファイル libhello.c /* libhello.c - ライブラリ使用の実例 */ #include void hello(void) { printf("Hello, library world.\n"); } ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.2. ファイル libhello.h /* libhello.h - ライブラリ使用の実例 */ void hello(void); ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.3. ファイル demo_use.c /* demo_use.c -- "hello" ルーチンを直接使用する実例 */ #include "libhello.h" int main(void) { hello(); return 0; } ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.4. ファイル script_static #!/bin/sh # 静的ライブラリの実例 # 静的ライブラリのオブジェクト libhello-static.o を作成します。 # 静的ライブラリと動的ライブラリの例を明確に区別するため、 # libhello-static という名前を使用しています。しかし、あなたの # オブジェクトファイルや静的ライブラリの名前に "-static" を使う # 必要はありません。 gcc -Wall -g -c -o libhello-static.o libhello.c # 静的ライブラリを作成します。 ar rcs libhello-static.a libhello-static.o # ここで、libhello-static.a を使うためにそれをどこか他の場所へ # 単にコピーすることもできます。デモが目的なので、ライブラリは # カレントディレクトリ内にとどめておきます。 # demo_use プログラムをコンパイルします。 gcc -Wall -g -c demo_use.c -o demo_use.o # demo_use プログラムを作成します。-L. により、プログラム作成中に # "." が検索されることになります。このコマンドは、libhello-static.a # 内の関係するオブジェクトを demo_use_static に組み込むということ # に注意してください。 gcc -g -o demo_use_static demo_use.o -L. -lhello-static # プログラムを実行します。 ./demo_use_static ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.5. ファイル script_shared #!/bin/sh # 共有ライブラリの実例 # 共有ライブラリのオブジェクトファイル libhello.o を作成します。 gcc -fPIC -Wall -g -c libhello.c # 共有ライブラリを作成します。 # libhello は C ライブラリに依存しているので、C ライブラリと # リンクさせるために -lc オプションを使います。 gcc -g -shared -Wl,-soname,libhello.so.0 \ -o libhello.so.0.0 libhello.o -lc # ここで、libhello.so.0.0 をどこかのディレクトリ、/usr/local/lib # など、に単にコピーできます。 # シンボリックリンクを修正するため、ldconfig を呼び出す必要があります。 # soname を設定します。単に、 # # ln -sf libhello.so.0.0 libhello.so.0 # # を実行するだけでもよいですが、ldconfig にやってもらいましょう。 /sbin/ldconfig -n . # linker name を設定します。 # より洗練された設定をおこなうなら、既に linker name が存在するかを # 確認し、もし存在すれば、それを残しておくべきか否かを調べます。 ln -sf libhello.so.0 libhello.so # demo_use プログラムをコンパイルします。 gcc -Wall -g -c demo_use.c -o demo_use.o # demo_use プログラムを作成します。-L. により、プログラム作成中に # "." が検索されることになります。これは、プログラム実行時に "." # が検索されることを意味 'しない' ということに注意してください。 gcc -g -o demo_use demo_use.o -L. -lhello # プログラムを実行します。LD_LIBRARY_PATH を使って、どこに共有 # ライブラリが存在するかをプログラムに教える必要があることに注 # 意してください。 LD_LIBRARY_PATH="." ./demo_use ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.6. ファイル demo_dynamic.c /* demo_dynamic.c -- 動的ローディングと "hello" ルーチン使用の実例 */ /* 動的にライブラリをロードするルーチン用に dlfcn.h が必要 */ #include #include /* "libhello.h" をインクルードする必要がないことに 注意してください。しかしながら、関連するものを 指定する必要はあります。dlsym() から得ようとして いる値を保持するタイプを指定する必要があります。*/ /* "simple_demo_function" タイプは、引数をとらず、 何も値を返さない関数を示しています。 */ typedef void (*simple_demo_function)(void); int main(void) { const char *error; void *module; simple_demo_function demo_function; /* 動的にロードされるライブラリをロードする */ module = dlopen("libhello.so", RTLD_LAZY); if (!module) { fprintf(stderr, "Couldn't open libhello.so: %s\n", dlerror()); exit(1); } /* シンボルを得る */ dlerror(); demo_function = dlsym(module, "hello"); if ((error = dlerror())) { fprintf(stderr, "Couldn't find hello: %s\n", error); exit(1); } /* DL ライブラリ内の関数を呼び出す */ (*demo_function)(); /* 全てが終了したので、物事をきれいに片付ける */ dlclose(module); return 0; } ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.7. ファイル script_dynamic #!/bin/sh # 動的にロードされるライブラリの実例 # libhello.so とその仲間が既に作成されているものとします # (共有ライブラリの例を見てください)。 # demo_dynamic プログラムファイルをオブジェクトファイルへ # コンパイルします。 gcc -Wall -g -c demo_dynamic.c # demo_use プログラムを作成します。 # このプログラムが使用する唯一の特別なライブラリは、プログラム # 起動後までロードされないので、DL ライブラリをどこに探しにい # けばよいかを教える必要がないことに注目してください。しかしな # がら、DL ライブラリをロードするライブラリを含めるため、-ldl # オプションは '必要' となります。 gcc -g -o demo_dynamic demo_dynamic.o -ldl # プログラムを実行します。LD_LIBRARY_PATH を使い、動的にロード # されるライブラリをどこで得られるかを教える必要があります。 LD_LIBRARY_PATH="." ./demo_dynamic ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7. その他の情報源 下記のものは、ライブラリに関して特に役に立つ情報を含んでいます。 ・ Daniel Barlow による ``The GCC HOWTO''. 特にこの HOWTO は、ライブ ラリ作成用のオプションと、どのようにライブラリに問い合わせをするか について論じています。ここでは取り上げていない情報を扱っていますが 、逆のことも言えます。この HOWTO は、 http://www.linuxdoc.org の Linux Documentation Project から入手できます (訳注:``The GCC HOWTO'' の日本語訳は http://www.linux.or.jp/JF/JFdocs/ GCC-HOWTO.html です)。 ・ The Tool Interface Standards (TIS) 委員会による ``Executable and Linkable Format (ELF)'' (実際にはこれは、同委員会による the Portable Formats Specification Version 1.1 内の一つの章です). これ は、ELF 形式 (Linux や GNU gcc に特化したものではありません) 及び、 その形式に関する大量の詳細情報を提供するものです。 ftp:// tsx-11.mit.edu/pub/linux/packages/GCC/ELF.doc.tar.gz を見てください 。 MIT からファイルを取得するなら、そのフォーマットが一般的ではない ことに注意してください。 gunzip と untar を実行すると、``hps'' ファ イルができます。ファイルの最初と最後の行を削除し、``ps'' ファイルに 名前を変更すれば、普通のファイル名を持つ印字可能な Postscript ファ イルを得られます。 ・ Hongjui Lu による ``ELF: From the Programmer's Perspective''. こ れは、ELF に関する Linux と GNU gcc に特化した情報を提供します。 ftp://tsx-11.mit.edu/pub/linux/packages/GCC/elf.ps.gz で取得できま す。 ・ (訳者による追加) 佐野武俊さんによる ``Linux C Library (libc) につ いて''. Linux C Library (libc) の概要について、その役割と歴史などを 簡単にまとめたものです。 http://www.linux.or.jp/JF/JFdocs/ libc-intro.html で参照できます。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8. 著作権とライセンス この文書の著作権は David A. Wheeler にあり (Copyright (C) 2000)、 GNU 一般公有使用許諾 (GPL) により保護されます。代価なしで再配布しても構いま せん。文書の原文を ``プログラム'' と解釈し、次の条件も守ってください。 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 本プログラムはフリー・ソフトウェアです。あなたは、Free Software Foundation が公表した GNU 一般公有使用許諾の「バージョン 2」或いは それ以降の各バージョンの中からいずれかを選択し、そのバージョンが定 める条項に従って本プログラムを再頒布または変更することができます。 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 本プログラムは有用とは思いますが、頒布にあたっては、市場性及び特定 目的適合性についての暗黙の保証を含めて、いかなる保証も行ないません 。詳細については GNU 一般公有使用許諾書をお読みください。 You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA あなたは、本プログラムと一緒に GNU 一般公有使用許諾の写しを受け取っ ているはずです。そうでない場合は、Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 0211-1307, USA へ手紙を書い てください。 これらの条項は、他のウェブサイトがミラーリングをすることを許可するもの ですが、 ・ あなたのミラーサイトがマスターサイトから最新情報を自動的に取得す るようにし、 ・ マスターサイトへのハイパーリンクと共にマスターサイトのロケーショ ン http://www.dwheeler.com/program-library を明示し、そして ・ 著者として、私 (David A. Wheeler) に謝辞をお願いします。 はじめの二つは、第一に、私が過去のバグについて繰り返し話を聞かされるこ とを防ぎます。単にあなたが文書を適切にミラーリングしていないという原因 のために、私は一年前に直したバグに関する話を聞きたくはありません。マス ターサイトへリンクを張ることにより、ユーザはあなたのサイトが最新のもの であるかどうかを確認できます。非常に厳しいセキュリティ要求があり、その ためにインターネットへ通常に接続するリスクを取ることができないサイトの 問題に対して、私は敏感です。このことがあなたの状況にあてはまるならば、 少なくとも、他のポイントへの接続を試みたり、時折あなたの環境へのスニー カーネット・アップデート (訳注:スニーカーネット (sneakernet) ―― FD 等を持ち運びすることにより情報を共有するネットワーク) を試みるなどして ください。 このライセンスによれば、あなたはドキュメントを変更しても構いませんが、 あなたが書いたものではないものをあなたのものであると主張したり (つまり 盗用です) 、変更されたバージョンが原作であるかのようなふりをすることは できません。著作物の変更は、著作物全体の著作権をあなたに譲渡するもので はありません。これは、著作権法の用語でいうところの ``public domain'' の 著作物ではありません。ライセンスを詳細に見てください。特に、 ``You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.'' ――``ファイルを変更し た旨とその変更日とを、変更したファイル上に明確に表示すること'' というこ とには注意してください。ライセンスがどのようなことを許可しているかにつ いて質問がある場合は、私に連絡を取ってください。たいていの場合には、あ なたの変更が他のみなさんの変更と共にマスターコピーへ統合されるよう、あ なたの変更を統合者 (現在は David A. Wheeler) へ送るのがよいでしょう。