2019年3月14日木曜日

NativeExtensions で Struct を使う方法

概要

Struct をメモリ空間に展開し、それに Ruby からアクセスする方法を紹介します

環境

  • CentOS 7.5.1804
  • Ruby 2.5.0p0

サンプルコード

NativeExtensions なのですべて C で書かれています
後述で説明します

#include <ruby.h>

typedef struct {
  char *name;
  int age;
} MyStruct;

static void my_struct_free(MyStruct *s) {
  free(s);
}

static VALUE my_struct_alloc(VALUE self) {
  MyStruct *s = ALLOC(MyStruct);
  return Data_Wrap_Struct(self, 0, my_struct_free, s);
}

static VALUE wrap_set(VALUE self) {
  MyStruct *s;
  Data_Get_Struct(self, MyStruct, s);
  s->name = (char *)"hawk";
  s->age = 10;
  return Qnil;
}

static VALUE wrap_show(VALUE self) {
  MyStruct *s;
  Data_Get_Struct(self, MyStruct, s);
  printf("%s\n", s->name);
  printf("%d\n", s->age);
  return Qnil;
}

void Init_firstext(void) {
  VALUE fe = rb_define_class("FirstExt", rb_cObject);
  rb_define_alloc_func(fe, my_struct_alloc);
  rb_define_method(fe, "set", wrap_set, 0);
  rb_define_method(fe, "show", wrap_show, 0);
}

説明

まずは今回の肝となる Struct を用意します
Struct 内に Struct は持たないようにしています

typedef struct {
  char *name;
  int age;
} MyStruct;

Ruby 側で FirstExt.new されたときに構造体の領域をメモリに確保する必要があります
そうしない状態で構造体にアクセスしようとすると Segmentation fault で落ちます
そのために rb_define_alloc_func(fe, my_struct_alloc); を定義します
これは new された際に my_struct_alloc メソットを呼び出し構造体のメモリ領域を確保します

my_struct_alloc は以下の通りです

static VALUE my_struct_alloc(VALUE self) {
  MyStruct *s = ALLOC(MyStruct);
  return Data_Wrap_Struct(self, 0, my_struct_free, s);
}

決まり文句のような感じです
Data_Wrap_Struct は C の構造体をラップして self クラスのインスタンスである Ruby オブジェクトを返します
これで構造体にアクセスできる準備はできました

Ruby から構造体にアクセスするためのメソッド set, show を用意します
そしてそれらは wrap_set, wrap_show を呼び出すように定義します

rb_define_method(fe, "set", wrap_set, 0);
rb_define_method(fe, "show", wrap_show, 0);

それぞれのメソッドを C 側で定義します
まずメモリ上に展開した構造体にアクセスするには Data_Get_Struct を使います
ポインタを準備することでそのポインタにアドレスを設定してくれます
あとはアロー演算子を使って構造体のメンバにアクセスすれば OK です
ここでポイントなのは構造体を参照する関数は必ず static VALUE wrap_show(VALUE self) というように引数と返り値に VALUE を使う必要があります
set は特に返り値はないですが、これを static void wrap_set(VALUE self) のように宣言してしまうといざ set をコールしたときに Segmentation fault が発生します

構造体自体をリターンするには

少し調べたのですが簡単にはいかなさそうです (というか無理そう?)
定義した構造体のメンバにアクセスするのが目的なのであれば各メンバを取得する Getter を定義してあげる感じになると思います
やり方がわかったら、また別の記事で招待したいと思います

最後に

NativeExtensions で Struct の基本的な扱い方を紹介しました
C 側にちゃんと領域を alloc してあげる必要があるのがポイントです
また構造体を参照する場合は Data_Get_Struct を使いますがこれを使う関数は VALUE な返り値と引数を必ず持つ点も重要です

0 件のコメント:

コメントを投稿