いものやま。

雑多な知識の寄せ集め

Ruby-FFIについて調べてみた。(まとめ)

これまでの各記事は、以下から。

基本的な使い方

基本的な流れは、以下のとおり。

# 1. 'ffi'をrequireする。
require 'ffi'

module MyLib
  # 2. 自作のモジュールをFFI::Libraryでextendする。
  extend FFI::Library

  # 3. FFI::Libray#ffi_libで、ロードするライブラリを指定する。
  ffi_lib 'c'

  # 4. Ruby-FFIで提供されている機能を使って、
  #    ライブラリのインタフェースとの結びつけを行う。
  attach_function :puts, [ :string ], :int
end

# 5. ライブラリの機能を呼び出す。
MyLib.puts 'Hello, World using libc!'

データ構造の記述

構造体/共用体

ライブラリで定義されている構造体/共用体のデータ構造を定義することで、そのメンバにアクセスできる。

例えば、次のような感じ。

class Point2D < FFI::Struct  # 共用体の場合、FFI::Unionを継承する
  layout(
    :x, :double,  # メンバ名とその型を書いていく
    :y, :double)
end

# 構造体のオブジェクトを作成
# 構造体のデータ自体はネイティブなメモリに確保される
point = Point2D.new

# メンバに代入
point[:x] = 1.0
point[:y] = 2.0

# メンバを参照
puts point[:x]  #=> 1.0
puts point[:y]  #=> 2.0

複雑なデータ構造

  • 構造体の中に配列を含む場合、[配列の型, 配列のサイズ]とする。
  • 構造体の中に別の構造体/共用体を含む場合、メンバの型として構造体名/共用体名を書く。
  • 構造体の中に別の構造体/共用体へのポインタを含む場合、メンバの型として(構造体名/共用体名).ptrを書くと、そのメンバの型が構造体/共用体のポインタであると理解して、うまいこと処理される。

オートリリース

オブジェクトが破壊されるタイミングでメモリの解放処理を行いたい場合、FFI::Structの代わりにFFI::ManagedStructを継承する。
そして、releaseクラスメソッドを実装する。

列挙型

列挙型の定義は、例えば以下のような感じ。

Day = enum(
  :sunday, 1,  # 列挙子とその値(値は省略可能)を書いていく
  :monday,
    ...
  :friday,
  :saturday)

インタフェースの結びつけ

グローバル変数の結びつけ

FFI::Library#attach_variableを使う。

例えば、以下のような感じ。

module MyLib
  ...
  attach_variable :errno, :int  # グローバル変数名とその型を書く
end

# アクセサメソッドでアクセスできるようになる
puts MyLib.errno

関数の結びつけ

FFI::Library#attach_functionを使う。

例えば、以下のような感じ。

module MyLib
  ...
  attach_function :puts, [:string], :int  # 関数名、引数の型のリスト、戻り値の型を書く
end

# モジュール関数として使えるようになる
MyLib.puts("Hello, world!")
  • 引数がない場合、空の配列を指定する。
  • 戻り値がない場合、型として:voidを指定する。
  • 引数/戻り値として構造体を扱うときには、次のようにするとうまいこと処理される。
    • 値渡しする場合、型に(構造体名).by_valueもしくは(構造体名).valを指定する。
    • 参照渡しする場合、型に(構造体名).by_refもしくは(構造体名).ptrを指定する。
  • 引数/戻り値が列挙型の場合、型として列挙型を書くと、うまいこと処理される。

ネイティブメモリの確保とアクセス

ネイティブなメモリを確保するには、FFI::MemoryPointerのオブジェクトを生成する。

そして、ポインタの指し示す先の、ネイティブメモリ上にあるデータを読んだり書いたりするには、FFI::Pointerのメソッドを利用する。(FFI::Pointerを継承しているので、FFI::MemoryPointerもこれらは利用できる)

例えば、次のような感じ。

pointer = FFI::MemoryPointer.new :int  # malloc(sizeof(int))と同等
buffer = FFI::MemoryPointer.new :char, 256  # malloc(sizeof(char) * 256)と同等

pointer.write_int(5)  # *pointer = 5と同等
data = buffer.read_bytes(10)  # bufferの先頭10バイトを読み込む

今日はここまで!