LoginSignup
1
2

More than 1 year has passed since last update.

ruby-ffi や Fiddle で、環境依存でサイズが変わる構造体に対処する

Last updated at Posted at 2021-10-01

はじめに

FFIで環境依存で変化する構造体に対処することは難しい。Win, Mac, Linuxなど大雑把に場合分けすることも考えられるが、ここでは別の方法として、sizeof(piyo) の結果を定数に保存する小さなC拡張を作成することを考える。

カレンダー日時を保存するtm構造体

ここでは、例として、日時を保存するtm構造体を扱う。tm構造体は、time.h をincludeすると使うことができる。
Rubyでは、ffiを扱うライブラリはfiddleFFI(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でもだいたい同じようなことができる。

この記事は以上です。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2