2013年9月19日木曜日

共有ライブラリでエクスポートしない

Windowsでは共有ライブラリ(DLL)を作るとき、
エクスポート(公開)する関数を明示的に指定しなければなりませんが、
Linuxの共有ライブラリ(so)ではstatic宣言してないものはすべてエクスポートしてくれるので、
めんどくさくなくていいな、などとずっと思っていたのですが、
それはそれでちょっと困ったことも起きてしまいます。
以下はLinuxでの話となります。

複数の共有ライブラリをリンクする場合、それぞれに同名の関数があると、
想定していない方を呼び出すこことがあります。
これはあるライブラリ内の関数から同じライブラリ内の関数を呼び出すつもりが、
別のライブラリ内の同名関数を呼び出す可能性があることを意味します。
もちろん各ライブラリ内でそれら同名関数をstatic宣言すればそんな問題は起こらないのですが、
1つのライブラリを構成するソースファイルが複数に分かれていたりして
そういう対応ができないケースも多々あります。
またCの関数をstaticにしておいて関数のポインタを別途取得して使うような
C++っぽい解決方法もあるにはありますが、
そもそも既存のコードを再利用したいような場合にはやりたくない変更となります。

複数ライブラリで同名関数があった場合の呼び出しの優先度はどうかというと、
これはコンパイル(リンク)時に決まってしまうようで、例えば
$ gcc main.c -ltest1 -ltest2
で生成したバイナリではlibtest1.soのものが優先され、
libtest2.soのものが呼ばれることはないことになります。
なお、すでにある実行可能ファイル"a.out"については
$ ldd a.out
の実行結果の先に表示されるライブラリがより優先度が高いことになります。
なお環境変数LD_PRELOADにフルパスで設定されたライブラリは
無条件で先に読み込まれるようになるため、無理やり優先度を高くすることができるようです。

もし共有ライブラリをソースからビルドできる環境があるなら、そんな微妙なことをしなくても、
Windowsのように指定した関数だけをエクスポートするだけで解決できるかもしれません。
実はgccはそんな機能を提供しています。
例えばマップファイル"test.map"を
{
  global: publicfunction;
  local: *;
};
のような内容で作成し、ライブラリのコンパイルを
$ gcc  -shared -fPIC -c test1.c -o libtest1.so -Wl,--version-script=test1.map
のようにすれば、publicfunction関数だけがエクスポートされます。
なお、
$ nm -g -C -D libtest1.so
を実行するとエクスポートされている関数名が確認できます。
ちなみに、さらにC++のメソッドをエクスポートしたい場合はマップファイルを
以下のようにすればいいようです。
{
  global:
    extern "C++" {      
      publicclass::publicclass*;
      publicclass::?publicclass*;
    };
    publicfunction;
  local: *;
};
ここで'?'は'~'が使えないことをカバーするためのワイルドカード(1文字)で、
publicclassメソッドの後ろにワイルドカード(0文字以上)が付いているのは
オーバーロードに対応するためのものと推察します。

共有ライブラリはとても便利ですが、場合によっては地獄に落とされると言う話でした。

0 件のコメント:

コメントを投稿