【Mac】Ruby1.9 と Python で共有ライブラリの関数、つまりはCやC++の関数を使う【Python】

動的ライブラリ(Mac だと.dylibなど。Linuxだと .so。要は /usr/lib/ に入っているもの)を Ruby から呼び出して使いたいことがある。Python だと ctypes という便利なモジュールがある。Ruby にも DL::Importer というものがあるようだ。というわけで使ってみた。

最初、

Ruby 1.8.7 リファレンスマニュアル > ライブラリ一覧 > dl/importライブラリ > DL::Importableモジュール http://doc.ruby-lang.org/ja/1.8.7/class/DL=3a=3aImportable.html

↑を見てみて、載っているコードをコピペして動かしてみるとエラーが出る。

結論から言うと二点誤っていた。

1. 今自分が使っている Ruby は、

piyo-MBP: piyo$ ruby1.9 -v
ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin12]

であり、上のリファレンスはあくまで 1.8.7 のものである。古い、と思って 1.9.3 を見てみるとすべてが ToDo と書いてあってヽ(`Д´)ノ。ドキュメントなんて誰も書きたくないですねそうですね(#・∀・)。(→追記、きちんとドキュメントありましたすみません。→ http://doc.ruby-lang.org/ja/1.9.3/library/dl=2fimport.html) そしてなんと、名前が変わっていた!1.8.7 だと、”extend DL::Importable” なのだが、1.9.3 だと、”extend DL::Importer” なのである。Ruby 1.9.3 で Importable のまま使うとエラーが出て動かないなんて罠だ。

2. Mac で試して見ているのだが、Mac の共有ライブラリ名は “libc.dylib”、であった。断じて “libc.so.6″ ではなかった。まあこれは私の知識不足だから許そう(何となく Mac と Linux はバイナリ互換なんじゃないかと思っていた(;・∀・))。

というわけで、修正してきちんと動くようにした Mac 用コードがこちら↓。Linux の人は .dylib を .so に書き換えれば良いと思う。ところで修正というより改変で、 printf を追加して使ってみた。extern 以下で、”int printf(const char *)” と書いているが適当に書いた。実際は、”int printf(const char *format, …)” だと思うが、”…”の扱いなどよくわからないので適当に区切って書いたら動いたのでよし↓とする。
# coding:utf-8

# condition: Mac OS (Mountain Lion), Ruby 1.9.3 (not 1.8.7!)

require "dl/import"

module LIBC
	extend DL::Importer
	dlload "libc.dylib"
	extern "int strlen(const char *)"
	extern "int printf(const char *)"
end

LIBC.printf("Hello, world") #=> Hello, world

なお、このモジュールの作者さんが書いたドキュメントと思われるものを見つけたのでリンクを貼っておく↓

http://ttsky.net/ruby/ruby-dl-doc-ja/rbdl.pdf


ちなみに、Python の ctypes を Mac で動かしてみたコードがこちら↓

# condition: Mac OS (Mountain Lion)

piyo-MBP: piyo$ python
Python 2.7.2 (default, Jun 20 2012, 16:23:33) 
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import *
>>> libc = cdll.LoadLibrary("libc.dylib")
>>> libc.printf("hello")
hello5
↑謎の「5」が入っているのは気にしない事にする…。けっこう気になるけれど。Python の ctypes の方が扱いが楽で良い感じ。記法も楽。しかしながら、公式サイトに↓、

>ctypesを使ってPythonをクラッシュさせる方法は十分なほどあるので、 よく注意すべき
http://www.python.jp/doc/2.5/lib/ctypes-calling-functions.html


と堂々と載っているので安定性が微妙なのかもしれない…。

ところでそもそも共有ライブラリを Ruby などで使う局面が何故あるのかといえば、

– ソースが公開されていない共有ライブラリを使わざるをえない
– ソースは公開されているが他の言語に書きなおすのが面倒
– ウェブプログラミングでそのライブラリ使いたいが、C/C++ で CGI を書く気力がない。Ruby や Python でさくっと書きたい

などの条件が揃ったときである。

—–追記@2012年9月19日—–
CentOS 6.3 でやってみたら↓こんな感じになった。どうやら、”libc.so” だとダメっぽい?代わりに “/lib/libc.so.6” なら行けた。

>>> libc = cdll.LoadLibrary("libc.so")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/ctypes/__init__.py", line 431, in LoadLibrary
    return self._dlltype(name)
  File "/usr/lib/python2.6/ctypes/__init__.py", line 353, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: /usr/lib/libc.so: invalid ELF header

>>> libc = cdll.LoadLibrary("/lib/libc.so.6")

>>> libc.printf("Hello, world!\n")
Hello, world!
14
>>> a = libc.printf("Hello, world!\n")
Hello, world!

>>> print a
14
また、↓こんな感じで共有ライブラリの場所を知ることが出来る。
[root@localhost ~] ldconfig -p | grep libc.so
	libc.so.6 (libc6, hwcap: 0x0028000000000000, OS ABI: Linux 2.6.18) => /lib/i686/nosegneg/libc.so.6
	libc.so.6 (libc6, OS ABI: Linux 2.6.18) => /lib/libc.so.6
—–追記ここまで—–