はじめに
FFIで環境依存で変化する構造体に対処することは難しい。Win, Mac, Linuxなど大雑把に場合分けすることも考えられるが、ここでは別の方法として、sizeof(piyo)
の結果を定数に保存する小さなC拡張を作成することを考える。
カレンダー日時を保存するtm構造体
ここでは、例として、日時を保存するtm構造体を扱う。tm構造体は、time.h
をincludeすると使うことができる。
Rubyでは、ffiを扱うライブラリはfiddle
とFFI(Ruby-FFI)
の2種類があるが、下記はFiddleを使った例である。
TM = struct [
'int tm_sec',
'int tm_min',
'int tm_hour',
'int tm_mday',
'int tm_mon',
'int tm_year',
'int tm_wday',
'int tm_yday',
'int tm_isdst'
]
この場合は、TMのサイズは4×9=36である。これはWindowsではうまく動作する。しかし、LinuxやMacでは必ずしもうまくいくとは限らない。
スタック・オーバーフローの質問 Size of struct tm
にあるように、Linuxでは、tm構造体には追加のフィールドが定義されてサイズは56である。TMのサイズは56にしなければならない。
Ubuntuでは /usr/include/x86_64-linux-gnu/bits/types/struct_tm.h
にtm構造体の定義がある。
/* ISO C `broken-down time' structure. */
struct tm
{
int tm_sec; /* Seconds. [0-60] (1 leap second) */
int tm_min; /* Minutes. [0-59] */
int tm_hour; /* Hours. [0-23] */
int tm_mday; /* Day. [1-31] */
int tm_mon; /* Month. [0-11] */
int tm_year; /* Year - 1900. */
int tm_wday; /* Day of week. [0-6] */
int tm_yday; /* Days in year.[0-365] */
int tm_isdst; /* DST. [-1/0/1]*/
# ifdef __USE_MISC
long int tm_gmtoff; /* Seconds east of UTC. */
const char *tm_zone; /* Timezone abbreviation. */
# else
long int __tm_gmtoff; /* Seconds east of UTC. */
const char *__tm_zone; /* Timezone abbreviation. */
# endif
};
Fiddleで書く場合は、たとえばこんな感じ。
TM = struct [
'int tm_sec',
'int tm_min',
'int tm_hour',
'int tm_mday',
'int tm_mon',
'int tm_year',
'int tm_wday',
'int tm_yday',
'int tm_isdst'
'long __tm_gmtoff',
'const char *__tm_zone',
]
このような環境依存をきちんと解決するためには、C拡張ライブラリを作成して、
#include "tmsize.h"
void
Init_tmsize(void)
{
struct tm tm;
rb_require("fiddle");
ID sym_mFiddle = rb_intern("Fiddle");
VALUE rb_mFiddle = rb_const_get(rb_cObject, sym_mFiddle);
rb_define_const(rb_mFiddle, "SIZEOF_STRUCT_TM", INT2FIX(sizeof(s)));
}
とすれば、Fiddleモジュールに、定数 Fiddle::SIZEOF_STRUCT_TM
が定義される。
この定数の値は、Windowsでは36、Linuxでは例えば56になる。
これを利用して
require 'tmsize'
ptr = Fiddle::Pointer.new(Fiddle::SIZEOF_STRUCT_TM)
tm = LibUI::FFI::TM.new(ptr)
とすればよい。こうすれば、
TM = struct [
'int tm_sec',
'int tm_min',
'int tm_hour',
'int tm_mday',
'int tm_mon',
'int tm_year',
'int tm_wday',
'int tm_yday',
'int tm_isdst'
# 追加のフィールドは無視
]
で大丈夫。今回はFiddleで書いたが、ruby-ffiでもだいたい同じようなことができる。
この記事は以上です。