共有ライブラリを使う

一部のプラットフォームでは、GHCはHaskellコードをビルドして共有ライブラリを作ることに対応している。共有ライブラリは動的(dynamic)ライブラリと言われることもある。特に、Windowsではダイナミックリンクライブラリ(DLL)と呼ばれる。

共有ライブラリは、あるコンパイル済みコードの一つのインスタンスを複数のプログラムで共有することを可能にする。対照的に、静的リンクの場合はコードが各プログラムにコピーされる。このため、共有ライブラリを使うことがディスク領域の節約になることがある。また、あるコードを使う複数のプログラムの間で、メモリ上に置かれたそのコードの単一のコピーを共有することも可能になる。共有ライブラリは、大きなプロジェクトで、特に部分ごとに異なるプログラミング言語で書かれる場合に、プロジェクトを構造化する方法として使われることもしばしばある。また、共有ライブラリは、色々なアプリケーションでプラグイン機構としても広く使われている。これは特にWindowsにおいて、COMを使って広く行なわれている。

GHCバージョン6.12において、共有ライブラリをビルドすることに対応しているのはLinux(x86およびx86-64アーキテクチャ)である。GHCバージョン7では、Windows(11.6. Win32のDLLをビルド・利用する を見よ)、FreeBSDとOpenBSD(x86およびx86-64)、Solaris(x86)、Mac OS X(x86およびPowerPC)への対応が追加された。

共有ライブラリをビルドしたり使ったりするのは、静的ライブラリをビルドしたり使ったりするのにくらべてやや複雑である。Cabalを使う場合は、この詳細の大部分は隠され、パッケージのconfigure時に--enable-sharedを使うだけで、そのパッケージを共有ライブラリとしてビルドしたり、共有ライブラリとしてビルドされた他のパッケージとリンクさせたりできる。コードをビルドするときの込み入った点は、そのコードが共有ライブラリで使われるのか、それとも、依存する外部パッケージの共有ライブラリ版を使うのかを区別する必要があることである。共有ライブラリを使う共有ライブラリや、共有ライブラリを使うプログラムをインストールしたり配布したりするときの込み入った点は、必要な共有ライブラリが全て実行時に適切な場所にあることを保証する必要があることである。

共有ライブラリを使うようにプログラムをビルドする

単純なプログラムを、ランタイムシステムと基本ライブラリ群(base libraries)について共有ライブラリを使うようにビルドするには、-dynamicフラグを使えばよい。

ghc --make -dynamic Main.hs

これには二つの効果がある。第一に、共有ライブラリ版の(baseなどの)Haskellパッケージコードにリンクできるようにコードをコンパイルする。第二に、リンク時において、パッケージの、静的版でなく共有版のライブラリをリンクする。これには明らかに、これらのパッケージが共有ライブラリ付きでビルドされている必要がある。対応プラットフォームでは全ての中核パッケージの共有ライブラリがGHCに付属しているが、それ以外のパッケージを(例えばCabalで)インストールするなら、それらも共有ライブラリ付きでビルド(Cabalなら--enable-shared)しなければならないだろう。

Haskellパッケージの共有ライブラリ

Haskellコードをビルドして共有ライブラリにし、他のHaskellプログラムから使えるパッケージを作ることができる。最も簡単な方法はCabalを使うことで、Cabalパッケージを--enalbe-shared付きでconfigureするだけでよい。

この手順を手動で行いたいか、あるいは独自のビルドシステムを書いているなら、いくつかの規約に従わねばならない。Haskellコードをエクスポートする共有ライブラリを、他のHaskellコードから使うためにビルドするのは、CのAPIをエクスポートし、Cのコードから使われる共有ライブラリをビルドするのに比べて少々複雑である。間違えると、通常リンクエラーが待っている。

まず、Haskell共有ライブラリはパッケージに入っていなければならない。どの共有ライブラリにどのモジュールが入るかを自由に割り振ることはできない。Haskell共有ライブラリはパッケージ境界に対応していなければならない。なぜなら、GHCは同じ共有ライブラリ(あるいは実行可能バイナリ本体)の中でのシンボルへの参照と、異る共有ライブラリの間のシンボルへの参照を別に扱うからである。インポートするそれぞれのモジュールについて、GHCはそれが局所的に(同じ共有ライブラリの中に)あるのか、それとも別の共有ライブラリにあるのかを知る必要がある。これを知るためにパッケージが使われる。-dynamicを使っている場合、別パッケージのモジュールは別の共有ライブラリにあると仮定され、同じパッケージ(あるいはデフォルトの「main」パッケージ)にあるモジュールは同じ共有ライブラリ(あるいは実行ファイル本体)にあると仮定される。

パッケージを使うときにGHCが期待する慣習は、大部分4.9.7. Haskellソースからパッケージをビルドするに書かれている。加えて、GHCは.hiファイルの拡張子が.dyn_hiであることを期待するのに注意。これ以外の要求事項はCライブラリの場合と同じであり、以下に記述されている。特に、-dynamic-fPIC-sharedの各フラグの使用である。

CのAPIをエクスポートする共有ライブラリ

Haskellコードを、より大きな多言語プロジェクトに含める場合、Haskellコードを一つの共有ライブラリにビルドするのは良い方法である。静的リンクの場合、最終リンクの段階をGHCに行わせることが推奨されるが、共有ライブラリでは、Haskellライブラリは他のあらゆる共有ライブラリとまったく同じように扱える。リンクは、通常のシステムのCコンパイラとリンカを使って行なえる。

GHCによって生成された共有ライブラリは、Haskell以外で書かれた他のプログラムからロードすることができるので、プラグインとして使うのに適している。もちろん、プラグインを構築するには、FFIを使ってC関数をエクスポートし、RTSの初期化に関する規則を守らなければならない。8.2.1.2. 他言語のコードから呼べるようなHaskellライブラリを作るを見よ。特に、Haskellの関数が呼ばれる前にプラグインを初期化できるように、共有ライブラリからC関数をエクスポートする必要がおそらくあるだろう。

CのAPIをエクスポートするHaskellモジュールを共有ライブラリへとビルドするには、-dynamic-fPIC-sharedフラグを使う。

ghc --make -dynamic -shared -fPIC Foo.hs -o libfoo.so

前と同じく、-dynamicフラグは、このライブラリがrtsおよびbaseパッケージの共有ライブラリ版にリンクされるように指定する。-fPICは、最終的に共有ライブラリに入るあらゆるコードについて必要である。-sharedフラグは、プログラムではなく共有ライブラリを作ることを指定する。より明快にするために、コンパイルとリンクの段階を別にしてみる。

ghc -dynamic -fPIC -c Foo.hs
ghc -dynamic -shared Foo.o -o libfoo.so

原理的には、リンク時において、-dynamicなしに-sharedを使うことが可能である。これは、rtsとすべての基本ライブラリとを、新しく作る共有ライブラリに対して静的にリンクするということである。これによって、極めて大きいものの単独動作する共有ライブラリが作られることになる。しかし、大部分のプラットフォームにおいては、これにはすべての静的ライブラリが、コードを共有ライブラリに含めることができるように-fPIC付きでビルドされていなければならない。しかし現時点で我々はそれを行なっていない。

警告: 共有ライブラリがHaskellのAPIをエクスポートする場合、それを直接別のHaskellプログラムにリンクしてそのAPIを使うことはできない。リンクエラーが発生するだろう。そうでなく、上の節にあるようにパッケージに仕立てなければならない。

実行時における共有ライブラリの発見

共有ライブラリを管理する上での最大の困難は、プログラムが実行時に必要なライブラリを見つけられるように諸々を配置することである。これがどのように働くかの詳細はプラットフォームによって異なる。特に、Unix ELF、Windows、Mac OS Xの三つの主要なプラットフォームである。

Unix

Unixには二つの機構がある。共有ライブラリは、標準的な、動的リンカの知っている場所にインストールすることができる。例えば大部分のシステムでは/usr/lib/usr/local/libである。もう一つの機構は、プログラムやライブラリ自体に埋めこまれた「runtime path」(rpathともいう)を使うことである。このパスは、絶対パスであってもよいし、少くともLinuxとSolarisにおいてはプログラムやライブラリ自体からの相対パスでもよい。理論的には、これによって、プログラムとライブラリ群を合わせて、どこに置いても使えるようにまとめることができる。

GHCには-dynloadというリンク時フラグがあり、これを使って実行時に共有ライブラリを見つける方法を選択することができる。現在、次の二つの方法がある。

sysdep

システム依存の方式。デフォルトのモードでもある。Unix ELFシステムでは、これはRPATH/RUNPATHエントリを共有ライブラリや実行ファイルに埋め込むことである。なかでも、rtsと各パッケージの共有ライブラリが見つかった場所への絶対パスを使う。従って、プログラムは即座に実行でき、必要なライブラリを発見できるはずである。一方、他のマシンで使う場合、そのマシンでライブラリが別の場所にインストールされているのであれば、この方法は適さないかもしれない。

deploy

この方式はランタイムパスを埋めこまない。共有ライブラリは、標準の場所か、あるいは環境変数LD_LIBRARY_PATHによって与えられたディレクトリに存在することが期待される。

LinuxおよびSolarisにおいて、依存しているライブラリのパスとして相対パスを使うには、適切な-rpathフラグをリンカに渡せばよい。

ghc -dynamic Main.hs -o main -lfoo -L. -optl-Wl,-rpath,'$ORIGIN'

これは、libfoo.soというライブラリがカレントディレクトリにあり、このプログラムが(訳注: 別の場所、あるいは別マシンに)配置されたときには実行ファイルmainと同じディレクトリで見付かることを仮定している。同様に、実行ファイルから相対的に見てサブディレクトリを使うことも可能である。例えば、-optl-Wl,-rpath,'$ORIGIN/lib'

この相対パス手法は、二つの-dynload方式のいずれでも使えるが、deploy方式で使うのがより有意義である。違いは、deploy方式では最終的なELF RUNPATH$ORIGINだけになるのに対して、sysdep方式ではRUNPATH$ORIGINの後にプログラムが依存する全てのパッケージ(例えばbasertsなど)のライブラリディレクトリをつなげたものになることである。そして、これらのライブラリディレクトリは典型的には絶対パスである。ELFの共有ライブラリや実行ファイルの中のRPATH/RUNPATH項目を見るには、readelf --dynamicというunixツールが便利である。

Mac OS X

Darwin/Mac OS Xでは、動的ライブラリにはビルド時に「install name」が付加されることが通常期待される。これはそのライブラリの最終的なインストールパスである。後にこのライブラリをリンクするライブラリや実行ファイルは、このライブラリを実行時に探索する場所としてこのパスを選ぶ(このライブラリがまだインストールされていない場合であっても)。ghcを直接使ってコンパイルする場合、デフォルトではライブラリがビルドされた場所がinstall nameとして設定される。-dylib-install-nameオプションを使うと、指定したパスでそれを上書きすることができる(Appleのリンカに-install_nameを渡すものである)。Cabalはこれを代行する。Cabalは、動的ライブラリのinstall nameとして、最終的なインストール場所の絶対パスを自動的に設定する。