いものやま。

雑多な知識の寄せ集め

Ruby-FFIについて調べてみた。(その3)

昨日の続き。

いよいよCインタフェースとの結びつけをやっていく。

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

グローバル変数の結びつけを行うには、FFI::Library#attach_variableを使う。
そうすると、グローバル変数にアクセスするアクセサメソッドが使えるようになる。

attach_variable (グローバル変数名), (グローバル変数の型)
# もしくは
attach_variable (アクセサメソッド名), (グローバル変数名), (グローバル変数の型)

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

module MyLib
  extend FFI::Library
  ffi_lib 'c'
  
  # errnoの結びつけ
  attach_variable :errno, :int
end

# errnoを参照する
puts MyLib.errno

関数の結びつけ

関数の結びつけを行うには、FFI::Library#attach_functionを使う。
そうすると、その関数をモジュール関数として使えるようになる。

attach_function (関数名), (引数の型の配列), (戻り値の型)
# もしくは
attach_function (モジュール関数名), (関数名), (引数の型の配列), (戻り値の型)

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

module MyLib
  extend FFI::Library
  ffi_lib 'c'
  
  # libcのputsをc_putsというモジュール関数名で結びつける
  attach_function :c_puts, :puts, [:string], :int
end

# libcのputsを呼び出す
MyLib.c_puts("Hello, world!")  #=> "Hello, world!"

voidの扱い

まず、引数をとらない関数の場合、引数の型の配列として空の配列を指定する。

例えば、次のようになる。

# int getchar(void);を結びつける
attach_function :getchar, [], :int

そして、戻り値がない関数の場合、戻り値の型として:voidを指定する。

# void exit(int);を結びつける
attach_function :exit, [:int], :void

構造体/共用体の扱い

関数に引数として構造体/共用体を渡す場合、値渡しと参照渡しの2通りの方法が存在する。

  • 値渡しをする場合、引数の型としてFFI::Struct#by_valueを使う。(FFI::Struct#valも可)
  • 参照渡しをする場合、引数の型としてFFI::Struct#by_refを使う。(FFI::Struct#ptrも可)

このように型を指定した場合、Ruby-FFIの方でうまいことやってくれるので、引数としてはどちらも構造体のオブジェクトを渡すだけでいい。

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

module MyLib
  extend FFI::Library
  ffi_lib 'mylib'

  class Point < FFI::Struct
    layout(:x, :double, :y, :double)
  end

  # 値渡しする関数、int set_point(Point p);を結びつける
  attach_function :set_point, [Point.by_value], :int

  # 参照渡しする関数、int get_point(Point *p);を結びつける
  attach_function :get_point, [Point.by_ref], :int
end

point = Point.new
point[:x] = 1.0
point[:y] = 1.0

# 呼び出すときには値渡しか参照渡しか気にせず、単にオブジェクトを渡せばいい
MyLib.set_point(point)
MyLib.get_point(point)

なお、参照渡しをする場合、引数の型として:pointerをしてもいい。 ただし、この場合、呼び出すときにFFI::Struct#pointerメソッドでポインタを渡すようにしなければならない。

module MyLib
  ...
  # 引数の型を:pointerとして結びつける
  attach_function :get_point, [:pointer], :int
end

...

# 引数の型がポインタなので、自分でFFI::Struct#pointerを呼び出す必要がある
MyLib.get_point(point.pointer)

関数の戻り値として構造体/共用体が返ってくる場合も、値そのものが返ってくる場合と、参照が返ってくる場合がある。
このときも引数の型の指定と同様に、by_valueなのかby_refなのかを指定する。
そうすれば、Ruby-FFIの方でうまいことやってくれるので、構造体のオブジェクトをそのまま受け取れる。

列挙型の扱い

関数の引数/戻り値の型が列挙型の場合、昨日書いた通りに列挙型を定義してあれば、引数/戻り値の型として列挙型を書いておくことで、Rubyのコードではシンボルだけを扱えばいいようになる。

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

module MyLib
  extend FFI::Library
  ffi_lib 'mylib'
  
  Type = enum(
    :triangle, 3,
    :rectangle,
    :pentagon)
  
  class Figure < FFI::Struct
    layout(
      :type, Type,
      :height, :uint,
      :width, :uint)
  end

  # オブジェクトを作成する関数
  # Figure create_figure(Type type, unsigned int height, unsigned int width);を
  # 結びつける
  attach_function :create_figure, [Type, :uint, :uint], Figure.by_value

  # オブジェクトの形を返す関数
  # Type get_figure_type(Figure *figure);を結びつける
  attach_function :get_figure_type, [Figure.by_ref], Type
end

# Ruby-FFIがうまいことやってくれるので、シンボルを扱えばいい
figure = MyLib.create_figure(:triangle, 5, 10)
p MyLib.get_figure_type(figure)  #=> :triangle

今日はここまで!