[Ruby] Fiddle: 構造体(Fiddle::CStruct)を解放する

2022/01/16に公開

RubyからCのライブラリを呼び出すのに使うfiddleについての記事です。引数に構造体ポインタを使う際の手順を残しておきます。

あまり文献が見当たらず、正しいかどうかよくわかりませんとお断りしておきます。

例題: time関数

timeは、Unix時間(エポック秒)を得る、Cの標準ライブラリの関数です。

#include <time.h>

time_t time( time_t *t );

ここでtime_tは、近年では一般にlong(8バイト符号付き整数)の別名であり、構造体というには大げさですが、ほかに引数のポインタに結果を入れてくれる類のお手軽そうな関数が思いつかなかったので time_t を構造体と思って例題にします。[1]

fiddle/import

fiddle/importで、timeをRubyから呼び出すコードを書いてみます。time_t は、longの値1個を持つ構造体として定義しました。

試した環境はRubyのDockerコンテナです (ruby:3.1-slim)

require 'fiddle/import'

module LibC
    extend Fiddle::Importer
    dlload 'libc.so.6'
    typealias "time_t", "long"
    extern 'time_t time(time_t *t)'
    Time_t = struct(['long value'])
end

time_ptr = LibC::Time_t.malloc
LibC.time(time_ptr)

p time_ptr.value  # time()によるエポック秒
p Time.now.to_i   # Rubyで答え合わせ

結果の例です。

1642258051
1642258051

freeする

以上で動きましたが、LibC::Time_t.mallocしたままfreeしていないのが何とも落ち着きません。Fiddle::CStruct#mallocした領域をfreeする文献にいまいち巡り合えなかったですが、以下のようにするのがよさそうな気配です。

require 'fiddle/import'

module LibC
    extend Fiddle::Importer
    dlload 'libc.so.6'
    typealias "time_t", "long"
    extern 'time_t time(time_t *t)'
    Time_t = struct(['long value'])
end

time_ptr = nil
begin
    time_ptr = LibC::Time_t.malloc
    LibC.time(time_ptr)
    p time_ptr.value
ensure
    Fiddle.free(time_ptr.to_i) if time_ptr
end

mallocを明示的に呼んだならばfree、そうでないならば要らなそうです。Rubyオブジェクトが先にあってそのポインタを得るようなケース (例: Fiddle::Pointer[str], Fiddle::Closure::BlockCaller 等) でfreeするとセグフォします。

脚注
  1. time()を使う際は一般に戻り値を使うことが多そうですが、今回は引数のポインタ渡しについて焦点を当てます。 ↩︎

Discussion