ruby-ffi は 動的にライブラリを呼び出せるRubyの拡張でバインディングライブラリをnative extension で書かなくても良い感じに呼び出せるようになる便利なライブラリ。 Rubyでは libffi を簡単に使えるのでちょっと頑張ればCのコードを呼び出せる。
FFI = foreign function interface、他言語の関数を呼び出すためのインターフェイス
Cで書かれたライブラリの関数を native extension をビルドしなくても呼び出せて便利だったりする。
去年書いた記事でlibffiを使って、MP4v2Tagの関数を呼び出してみたけど、あんまり細かいところまで書かなかったので掘り下げて書いてみます。
とりあえずHello World
ruby-ffi はgemで用意されているので、bundlerとかで入れるといいと思う。
source "https://rubygems.org" gem 'ffi'
bundle install --path=vendor/bundle
これでlibffiをRubyから呼び出す準備ができたので、Rubyのコードを書いていくことにする。
まずはruby-ffiのサンプルにも載っているlibcのputs関数を呼び出してみる。
require 'ffi' module LibC # <- 1 extend FFI::Library # <- 1 ffi_lib FFI::Library::LIBC # <- 2 attach_function :puts, [ :string ], :int # <- 3 end LibC::puts 'Hello World!!' # <- 4
いつもどおりbundle exec ruby libc/hello_world.rbのように呼び出せばHello World!!が表示されるのが確認できる。
ffiの書き方は、
- 好きな名前で
moduleを定義して、FFI::Libraryをextendする ffi_libに呼び出す関数を含むライブラリの名前を渡す。(libcはFFI::Library::LIBCとして定義されている)attach_functionで関数、引数の型、戻り値を渡すLibC::putsのように呼び出す
といった形になります。
もう少し複雑な例
require 'ffi' module LibC extend FFI::Library ffi_lib FFI::Library::LIBC typedef :pointer, :FILE attach_function :fopen, [:string, :string], :FILE attach_function :fclose, [:FILE], :int attach_function :fputs, [:string, :FILE], :int end handle = LibC::fopen '/tmp/hogehoge', 'w' LibC::fputs 'hogehoge', handle LibC::fclose handle
上はファイルを書き出す例。fopen, fclose, fputsを呼び出す。FILEはtypedefで用意するとよい。
LibC::fopen から返ってきたポインタを LibC::fputsに渡しファイルに書き出し、LibC::closeでハンドルを閉じるっていうCのサンプルみたいなことをやっている。
こんな風に、ポインタを使い回すだけなら :pointer を使って簡単にかける。
構造体のポインタ
MP4v2のバインディングで書いた構造体はこんな感じになっていて、FFI::Structを継承してlayoutを使って構造体っぽいクラスを作っていく。
# Mp4v2 binding module
module Mp4v2
module Native
# Mp4v2 Tag struct
class MP4Tags < FFI::Struct
layout :__handle, :pointer,
:name, :string,
:artist, :string,
:album_artist, :string,
:album, :string,
:grouping, :string,
:composer, :string,
:comments, :string,
:genre, :string
def self.keys
[
:name, :artist, :album_artist, :album,
:grouping, :composer, :comments, :genre
]
end
end
end
end
構造体ではなくクラスなので、好きなように関数を追加してもよかったりして便利っぽい。typedef :pointer, :MP4Tagsのようにすることで、ポインタが構造体的なクラスと対応するようになる。
他にもいろいろできる
ffiのWikiを見ると、Win32APIの関数を呼び出す方法とか、callback関数を渡す方法とか、enumとかあったりして他にもいろいろできるっぽい。 けど、そろそろ眠くなってきたのでこの辺で一旦終わりにしたい。 コードはgistに書いたのであとはwikiを見ながらよろしくって感じです。