2019年3月16日土曜日

NativeExtensions を gem ファイルにパッケージする方法

概要

通常の gem を作成する方法とほぼ変わりませんが rake compile したり gemspec ファイルの編集が必要になります
プロジェクトの構成や rake-compiler については前回の記事を確認してください

環境

  • macOS 10.14.3
  • Ruby 2.5.1p57

.gemspec の編集

spec.extensions を追記します

Gem::Specification.new do |spec|
  spec.name          = "firstext"
  spec.version       = FirstExt::VERSION
  spec.extensions    = ["ext/firstext/extconf.rb"]
  # ...
end

途中は長いので省略しています
好きな箇所に追記してもらえれば OK です

コンパイル

最新の .so or .bundle ファイルを作成します

  • bundle exec rake compile

git commit

ビルドする前に gem に含めるファイルをコミットしなければいけません

  • git add .
  • git commit -m "add resources"

ビルド

gem ファイルを作成します
通常の gem を作成する方法と同じコマンドで作成できます

  • bundle exec rake build

これで pkg/firstext-0.1.0.gem が作成されます

ローカルインストール

ローカルでテストしたい場合は実施します

  • cd pkg
  • gem install -l firstext-0.1.0.gem

gem の作り直し

  • bundle exec rake clobber
  • bundle exec rake build
  • gem uninstall firstext
  • gem install -l firstext-0.1.0.gem

make install でインストールしてしまった場合は .so ファイルなどがすでに配置されてしまっており、そちらが優先されている可能性があります
macOS の場合は

  • rm -rf /usr/local/lib/ruby/site_ruby/2.5.0/x86_64-darwin17/*

あたりを実行してから gem install してください

その他

NativeExtensions の場合ヘッダファイルやライブラリファイルのインストールが事前に必要な場合があるので apt or yum でインストールしておきましょう
apt や yum でインストールできないライブラリもあるので、その場合は手動で適切なパスに配置してあげます
大抵は必要なヘッダファイルやライブラリは gem のドキュメントに記載されているはずです
ない場合は直接 extconf.rb などを見るしかないと思います

最後に

NativeExtensions を gem ファイルにする方法を紹介しました
あとは通常の rake release の手順で公開することができます

ハマりそうなのは git commit が必要な点かなと思います
未コミットのファイルは対象外になるのでファイルが見つからないなどのエラーが出る場合はちゃんとコミットされているか確認してみてください

2019年3月15日金曜日

NativeExtensions のテスト書く方法

概要

前回 NativeExtensions で Struct を使う方法を紹介しました
ライブラリを書けば当然テストしたくなります
NativeExtensions の場合少しクセがあるのでやり方の流れを紹介します

環境

  • CentOS 7.5.1804
  • Ruby 2.5.0p0

必要なファイル

spec ファイル関連のファイルが必要になります
bundle gem でプロジェクトを作成する際に -t オプションがあればテストに必要なファイルを一通り作成してくれます

.rspec
spec/firstext_spec.rb
spec/spec_helper.rb

rake-compiler のインストール

これを使ったほうが簡単 (というか公式のやり方) なのでこれを使います

  • vim firstext.gemspec
spec.add_development_dependency "rake-compiler"
  • bundle install --path vendor

Rakefile の編集

compile タスクを使えるようにします

  • vim Rakefile
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "rake/extensiontask"

Rake::ExtensionTask.new "firstext" do |ext|
  ext.lib_dir = "lib"
end

RSpec::Core::RakeTask.new(:spec)

task :default => :spec

ポイントは ext.lib_dir = "lib" の部分です
このパスに Makefile から作成される so ファイルを配置します
このパスに配置することで require 'firstext' できるようになります
ただ、このパスにはすでに lib/firstext.rb があります
どうやらこのファイルが優先されるようで、これがあるとせっかく C で実装したコードが参照されません
なので削除しておきます

  • rm lib/firstext.rb

rb ファイルを残したい場合は rake compile 時に吐き出されるファイル名を変更する方法もあります
が、その場合は require するのも吐き出されるファイル名になるのでご注意ください

lib/firstext/version.rb

module になっている部分を class に変更します
ここが module のままだと module が優先されて NativeExtensions 側のクラスが参照できなくなります

  • vim lib/firstext/version.rb
class FirstExt
  VERSION = "0.1.0"
end

spec/spec_helper.rb

特に何もしません

spec/firstext_spec.rb

ではテストを書きましょう
前回 NativeExtensions で実装した関数を呼び出しているだけです

  • vim spec/firstext_spec.rb
RSpec.describe FirstExt do
  it "has a version number" do
    expect(FirstExt::VERSION).not_to be nil
  end

  it "create a instance" do
    cli = FirstExt.new
    cli.set
    cli.show
  end
end

動作確認

  • bundle exec rake spec

でテストが動くはずです
うまく NativeExtensions で作成したクラスが参照できない場合は rake copile で so ファイルを再配置してみてください
もしくは so ファイル自体が指定のパスにあるか確認してください

また ext/firstext 配下に firstext.o や firstext.so などの成果物がある場合は削除しましょう
rake compile がエラーを吐きうまく動かないケースがあるようです

最後に

NativeExtensions で RSpec を使ってテストを書く方法を紹介しました
成果物の so ファイルを rb ファイルの代わりに配置するのがポイントです

NativeExtensions を変更した場合は再度 rake compile して成果物をデプロイする必要があります
あとは普通に spec テストを書いていけば OK です

参考サイト

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 な返り値と引数を必ず持つ点も重要です

2019年3月13日水曜日

NativeExtensions を開発するときに独自のヘッダファイルと so ファイルを使う方法

概要

apt や yum でインストールできるライブラリは特に気にする必要はありませんが場合によってはそれらを使ってライブラリをインストールできないことがあると思います
.h ファイルと .so ファイルが配布されておりそれらを使って NativeExtensions を開発したいことはあると思います
今回はそのような場合に .h と .so ファイルを参照する方法を紹介します

環境

  • CentOS 7.5.1804
  • Ruby 2.5.0p0

extconf.rb を編集する

extconf.rb は Makefile を作成するためのファイルです
事前に関数の存在チェックやヘッダファイルの存在チェックができます
ここで自分が作成したオリジナルのライブラリのチェックなどを行います

require "mkmf"

abort "missing malloc()" unless have_func "malloc"
abort "missing free()"   unless have_func "free"

dir_config('mylib', '/usr/lib/mylib/include', '/usr/lib/mylib/lib64')
if have_header("mylib.h") and have_library('mylib')
  create_makefile "mylib/mylib"
end

あとはヘッダファイルを /usr/lib/mylib/include/mylib.h に配置し so ファイルを /usr/lib/mylib/lib64/mylib.so に配置すれば OK です
もし配置していない場合は extconf.rb の実行中にエラーになるため NativeExtensions のインストールに失敗します

また上記のように書いておけば include と lib64 のパスを自分で指定することもできるようになります

  • ruby extconf.rb --with-mylib-include=/path/to/include --with-mylib-lib=/path/to/lib64

こんな感じです
これで Makefile が作成されるのであとは make すれば NativeExtensions が作成されます

参考サイト

2019年3月12日火曜日

Struct でポインタのポインタを扱う方法

概要

前回 複数のポインタを配列を管理する方法を紹介しました
今回は更にそれを Struct で管理する方法を紹介します

環境

  • macOS 10.14.3
  • Ruby 2.5.1p57

サンプルコード

コード全体は以下の通りです

require 'inline'

class MyTest
  inline do |builder|
    builder.add_compile_flags '-x c++', '-std=c++11'
    builder.include '<stdio.h>'
    builder.include '<malloc/malloc.h>'
    builder.prefix '
      typedef struct {
        int count;
        uint8_t *ary[];
      } Data;
    '
    builder.c_singleton '
      VALUE allocate() {
        Data *p = ALLOC(Data);
        return Data_Wrap_Struct(self, NULL, free, p);
      }
    '
    builder.c %q[
      void array_in_array() {
        Data *p;
        Data_Get_Struct(self, Data, p);
        p->count = 3;
        uint8_t *buf1 = (uint8_t *)malloc(16 * sizeof(uint8_t));
        for (unsigned long i = 0; i < malloc_size(buf1); i++) {
          buf1[i] = 'a';
        }
        p->ary[0] = buf1;
        uint8_t *buf2 = (uint8_t *)malloc(16 * sizeof(uint8_t));
        for (unsigned long i = 0; i < malloc_size(buf2); i++) {
          buf2[i] = 'b';
        }
        p->ary[1] = buf2;
        uint8_t *buf3 = (uint8_t *)malloc(16 * sizeof(uint8_t));
        for (unsigned long i = 0; i < malloc_size(buf3); i++) {
          buf3[i] = 'c';
        }
        p->ary[2] = buf3;
      }
    ]
    builder.c %q[
      void show() {
        Data *p;
        Data_Get_Struct(self, Data, p);
        uint8_t **ary = p->ary;
        for (int i = 0; i < p->count; ++i) {
          uint8_t *buf = ary[i];
          for (int j = 0; j < 16; ++j) {
            printf("%c", buf[j]);
          }
          printf("\n");
        }
      }
    ]
  end
end

p = MyTest.new
p.array_in_array
p.show

ポイント解説

Struct の定義で可変長配列 uint8_t *ary[]; を定義しておきます
そして C 側で格納したいポインタを生成して格納する際に Data_Get_Struct で取得したポインタを使って直接配列に格納します

あとは取得する側の関数で uint8_t **ary = p->ary; として取得し多重配列を扱うようにループさせれば OK です

macOS の場合上記でエラーにならないのですが Linux などの環境だと free(): invalid next size (fast) が発生する場合があります
その場合は uint8_t **ary; として Struct 内で配列を参照するポインタを準備すれば OK です
ちなみに macOS でポインタ版にするとぬるぽになります

メモリ空間の扱い方が macOS と Linux だと違うのかもしれません
この辺りは OS を考慮する必要があるので少し面倒になるかもしれません

2019年3月10日日曜日

RubyInline で配列 in 配列を扱う方法

概要

C 側で生成した多重配列を Ruby 側で使えるように変換するサンプルです

環境

  • macOS 10.14.3
  • Ruby 2.5.1p57

サンプルコード

# coding: utf-8
require 'inline'

class MyTest
  inline do |builder|
    builder.add_compile_flags '-x c++', '-std=c++11'
    builder.include '<stdio.h>'
    builder.include '<malloc/malloc.h>'
    builder.c %q[
      VALUE array_in_array() {
        // ポインタを管理する配列
        uint8_t *ary[3];
        // 3 つポインタを作成し適当に値を設定
        uint8_t *buf1 = (uint8_t *)malloc(16 * sizeof(uint8_t));
        for (unsigned long i = 0; i < malloc_size(buf1); i++) {
          buf1[i] = 'a';
        }
        ary[0] = buf1;
        uint8_t *buf2 = (uint8_t *)malloc(16 * sizeof(uint8_t));
        for (unsigned long i = 0; i < malloc_size(buf2); i++) {
          buf2[i] = 'b';
        }
        ary[1] = buf2;
        uint8_t *buf3 = (uint8_t *)malloc(16 * sizeof(uint8_t));
        for (unsigned long i = 0; i < malloc_size(buf3); i++) {
          buf3[i] = 'c';
        }
        ary[2] = buf3;
        // 設定したポイントを Ruby に返却する配列に変換する
        VALUE result = rb_ary_new2(3);
        for (int i = 0; i < 3; i++) {
          uint8_t *b = ary[i];
          VALUE r = rb_ary_new2(10);
          // 3 つポインタを変換し返却する配列に格納する
          for (int j = 0; j < malloc_size(b); j++) {
            rb_ary_store(r, j, rb_int_new(b[j]));
            printf("%c", b[j]);
          }
          rb_ary_store(result, i, r);
          printf("\n");
        }
        free(buf1);
        free(buf2);
        free(buf3);
        return result;
      }
    ]
  end
end

p = MyTest.new
result = p.array_in_array
result.each { |r|
  r.each { |buf|
    print buf.chr
  }
  puts
}

解説

今回作成される多重配列は以下のようになります

[
  ['a', 'a' ... 16個],
  ['b', 'b' ... 16個],
  ['c', 'c' ... 16個],
]

まず C 側でポインタを 3 つ生成しています
ポインタは 16 個の領域を持っています
それぞれに適当な char を設定していきます
設定した後に更にそれらのポインタを管理する配列 (ary) に格納します

C 側の多重配列の準備ができたらそれらを Ruby で使える多重配列に変換します
rb_ary_new2 で配列を作成し、作成した配列に対して rb_ary_store を使ってデータを格納していきます
あとは不要になった C 側のポインタ配列を開放します

実行してみるとわかりますが C 側で表示している情報と Ruby 側で表示している結果が同じになると思います

最後に

inline を使って多重配列を扱う方法を紹介しました
ruby.h の使い方に慣れていれば仕組み的にはそこまで難しいやり方ではないかなと思います

2019年3月9日土曜日

C の配列を Ruby に変換する方法

概要

前回 RubyInline で Struct の使い方を紹介しました
今回は配列を主に扱いたいと思います
Ruby で定義した配列を C で扱う方法と逆に C で定義した配列を Ruby で扱えるようにする方法も紹介したいと思います
また環境は macOS になるので Linux などで実行する場合は、若干 C の命令が異なるので注意してください

環境

  • macOS 10.14.3
  • Ruby 2.5.1p57

サンプルコード

手軽に試せる inline を使っています

require 'inline'

class MyTest
  inline do |builder|
    builder.add_compile_flags '-x c++', '-std=c++11'
    builder.c_raw '
      VALUE new_c_ary() {
        VALUE result = rb_ary_new2(10);
        for (int i = 0; i < 10; i++) {
          rb_ary_store(result, i, rb_int_new(i + 1));
        }
        return result;
      }
    '
  end
end

p = MyTest.new
c_ary = p.new_c_ary
puts c_ary.class
puts c_ary

まず Ruby に返せるように rb_ary_new2 で配列を作成し、それに対して rb_ary_store で値を格納しています
格納する際にも明示的な型の指定が必要で rb_int_new を使って int を配列に格納しています
これで return すれば C 側で生成した int の配列を Ruby 側で配列を扱うことができます

uint8_t 型のポインタ配列を変換する

今度はポインタ配列のケースを考えます
基本的には先程と同じでポイントの値を Ruby で扱えるデータに変換して配列に格納し直します

require 'inline'

class MyTest
  inline do |builder|
    builder.add_compile_flags '-x c++', '-std=c++11'
    builder.include '<malloc/malloc.h>'
    builder.include '<stdio.h>'
    builder.c_raw %q[
      VALUE new_uint8t_ary() {
        VALUE result = rb_ary_new2(16);
        uint8_t *buf = (uint8_t *)malloc(16 * sizeof(uint8_t));
        for (unsigned long i = 0; i < malloc_size(buf); i++) {
          buf[i] = '@'; // 40 (hex)
          rb_ary_store(result, i, CHR2FIX(buf[i]));
          printf("%ld: %02x\n", i, buf[i]);
        }
        free(buf);
        // VALUE ret = ULONG2NUM(malloc_size(buf));
        return result;
      }
    ]
  end
end

p = MyTest.new
ret = p.new_uint8t_ary
ret.each_with_index { |r, i|
  puts sprintf("%d: %02x", i, r)
}

uint8_t は C99 から追加された unsigned int の 8 ビット型です
16 個の領域を持つポインタを malloc で定義します
uint8_t *buf = (uint8_t *)malloc(16 * sizeof(uint8_t));
確保された領域のサイズは malloc_size(buf) で取得できます
macOS の場合 <malloc/malloc.h> を使いますが Linux 環境の場合は <malloc.h> で参照できます

確保された領域分ループしポインタのアドレスを 1 つずつずらし値を格納していきます
今回は @ を追加しています
アスキーコードで 64 で 16 進数表示で 40 になります
Ruby 用の配列に値を格納するときは CHR2FIX を使います
CHR2FIX は char 型の整数 buf[i] を Ruby の Fixnum に変換してくれるマクロです

あとは配列を return すれば OK です
Ruby 側で参照しても同じ結果になることが確認できると思います

逆に C 側で Ruby の配列を参照するには

Ruby 側で生成した Array クラスのオブジェクトを今度は C 側の uint8_t 型のポインタに格納してみます

require 'inline'

class MyTest
  inline do |builder|
    builder.add_compile_flags '-x c++', '-std=c++11'
    builder.include '<stdio.h>'
    builder.c %q[
      void show_ruby_ary(VALUE ary) {
        // long len = RARRAY_LEN(ary);
        struct RArray *a = RARRAY(ary);
        long len = a->as.heap.len;
        VALUE *p = RARRAY_PTR(ary);
        uint8_t *buf = (uint8_t *)malloc(len * sizeof(uint8_t));
        for (long i = 0; i < len; i++) {
          buf[i] = NUM2CHR(p[i]);
          printf("%ld: %02x\n", i, buf[i]);
        }
        free(buf);
      }
    ]
  end
end

ary = 16.times.map { |i| '@'.ord }
ary.each_with_index { |r, i|
  puts sprintf("%d: %02x", i, r)
}
p = MyTest.new
p.show_ruby_ary(ary)

inline を使っている場合ですが builder.c_raw ではなく builder.c を使います
c_raw は記載した C のコードをそのまま使うのですが c は inline 側で必要な型変換などを自動で行ってくれます
もし c_raw で定義したい場合は VALUE show_ruby_ary(int argc, VALUE *argv, VALUE self) のような感じで関数を定義する必要があります

ポイントは struct RArray *a = RARRAY(ary); で C で使える構造体 RArray に変換している点です
こうすることで配列の長さや値を管理するポインタを参照できるようになります
今回の場合 @ の文字コードを Ruby 側で代入しているので同じように文字コードとして認識されるために NUM2CHR を使ってコンバートしています

実行してみると同じような結果が表示されると思います

Struct を使って共有するには

だいぶ長くなりますが Struct を使って配列のデータを Ruby と C 間で共有する方法も紹介します
この方法を使えば C 側でも Struct がメモリ空間に保存されるので別の関数からでも Struct に保存した配列データを参照することができます

require 'inline'

class MyTest
  inline do |builder|
    builder.add_compile_flags '-x c++', '-std=c++11'
    builder.include '<stdio.h>'
    builder.include '<malloc/malloc.h>'
    builder.prefix '
      typedef struct {
        uint8_t *buf;
      } Data;
    '
    builder.c_singleton %q[
      VALUE allocate() {
        Data *p = ALLOC(Data);
        return Data_Wrap_Struct(self, NULL, free, p);
      }
    ]
    builder.c %q[
      void data2struct() {
        Data *p;
        Data_Get_Struct(self, Data, p);
        uint8_t *buf = (uint8_t *)malloc(16 * sizeof(uint8_t));
        for (unsigned long i = 0; i < malloc_size(buf); i++) {
          buf[i] = '@';
        }
        p->buf = buf;
      }
    ]
    builder.c_raw %q[
      static VALUE buf(int argc, VALUE *argv, VALUE self) {
        Data *p;
        Data_Get_Struct(self, Data, p);
        VALUE result = rb_ary_new2(malloc_size(p->buf));
        for (unsigned long i = 0; i < malloc_size(p->buf); i++) {
          rb_ary_store(result, i, CHR2FIX(p->buf[i]));
        }
        return result;
      }
    ]
  end
end

p = MyTest.new
p.data2struct
p.buf.each { |b|
  puts b.chr
}

data2struct で C 側で配列データを作成し struct Data のフィールド buf に値を保存します
配列のサイズは 16 でそれぞれに @ を格納しています

次に buf メソッドを作成します
本来は builder.accessor 'buf', 'uint8_t *' としてアクセサで定義したいのですが inline の場合できません
inline ないで @type_map があらかじめ定義されておりそこに uint8_t * がないためです
なので、Struct に保存した buf を取得することができる関数を自分で作成している感じです

buf は c_raw で定義しています
やっていることは単純で rb_ary_store を使って Ruby に返す用の配列に Struct に保存した buf の値を 1 つ 1 つ格納しているだけです
これで Ruby 側で buf の内容を表示すると C 側で格納した @ が 16 個表示されると思います

最後に

Ruby と C 間で配列データをやり取りする方法をいろいろと考えてみました
ポイントは必ず Ruby および C で扱える型に変換してあげる必要があることです
今回のサンプルでは配列に含める型は完全に決め打ちですが、Check_Type というマクロが用意されているのでそれに応じて挙動を変更することも可能です
この辺りの C とのやりとりは ruby.h に使われているマクロや関数を使っています
NativeExtensions を開発するときも同じような技術が必要になるので、ruby.h を使えるようになるのがてっとり早いと思います

今回のサンプルは inine を使っているので NativeExtensions ではそのまま使えませんが使える箇所は多いと思います

参考サイト