Ruby_extLib 2 - gfd-dennou-club/mrubyc-esp32 GitHub Wiki

[準備] C 言語で Ruby 拡張ライブラリの作成

Ruby のモジュールやクラスを C 言語で書くことができる. 高速に処理したい部分を C 言語で実装することは良くあることである.

前準備

C コンパイラ

Ruby のコンパイルに用いたものと同じものが必要となる.Debian (Linux) では gcc が使われているので,gcc パッケージをインストールしておけば良い.

ruby.h, mkmf.rb

Ruby をソースコードから自分でビルドした場合はインストールされているが, バイナリパッケージを導入した場合には ruby.h や mkmf.rb がインストールされているか確認すること. Debian (Linux) では ruby.h や mkmf.rb は開発用パッケージで提供されているので, apt install コマンドで ruby-dev パッケージをインストールすれば良い.

$ sudo apt update
$ sudo apt install ruby-dev

利用する関数

モジュールを定義する場合

モジュールを作成するには以下の関数を用いる.

VALUE rb_define_module(const char *name);
  • 引数はモジュールの名前
    • VALUE 型構造体は Ruby のオブジェクトを格納している構造体

C 言語の関数を Ruby のモジュール関数として登録するにはいくつかの方法があるが, 以下が最も簡単である.

void rb_define_module_function(VALUE module, const char *name, VALUE (*func)(), int argc);
  • 第 1 引数:関数を追加するモジュールを格納したVALUE型構造体
  • 第 2 引数:Ruby におけるメソッド名
  • 第 3 引数:実際に呼び出す C 言語の関数を与える.関数の型は VALUE 型.
  • 第 4 引数:呼び出す関数の引数の数.self というオブジェクトが第 1 引数として渡されるため、 ラッパー関数に実際に渡される引数の数はこれより 1 つ多くなる.

クラスを定義する場合

モジュールを作成するには以下の関数を用いる.これはクラス super の下位クラス name を作成するという意味である.

VALUE rb_define_class(const char *name, VALUE super);
  • 第 1 引数:モジュールの名前
  • 第 2 引数:上位クラス
    • VALUE 型構造体は Ruby のオブジェクトを格納している構造体

C 言語の関数を Ruby のクラスメソッドとして登録する.

void rb_define_method(VALUE class, const char *name, VALUE (*func)(), int argc);
  • 第 1 引数:関数を追加するクラスを格納した VALUE 型構造体
  • 第 2 引数:Ruby におけるメソッド名
  • 第 3 引数:実際に呼び出す C 言語の関数を与える.関数の型は VALUE 型.
  • 第 4 引数:呼び出す関数の引数の数.self というオブジェクトが第 1 引数として渡されるため、 ラッパー関数に実際に渡される引数の数はこれより 1 つ多くなる.

拡張ライブラリ作成の具体例 (1) : "Hello World"

拡張ライブラリ化 : モジュール版

"Hello World" を出力するプログラムを作成する. ここでは C 言語で Greeter という名のモジュールを定義し, そこに hello というメソッドを加えることにする. hello メソッドの中身を前述の hello.c と同様な内容とする.

$ mkdir -p ~/ruby-extlib/02
$ cd ~/ruby-extlib/02

C 言語でモジュールを定義する場合は,以下のようにプログラム (hello1.c) を書き換える.

#include "ruby.h"   // ruby.h のインクルードは必須

/* C 言語での "Hello World"*/
void c_hello(){
  printf("Hello, world!\n");
}

//ラッパー関数
// * 関数の型は常に VALUE
// * 関数の引数の型は常に VALUE.第一引数の名前は self にしておくこと.
// * 今は特に何も戻り値が必要でないので,Rubyの「nil」に 対応する「Qnil」という定数を与えている.
VALUE ruby_hello(VALUE self){
  c_hello();
  return Qnil;
}

//ライブラリの初期化関数
// * Ruby はライブラリを読み込んだらまず 「Init_ライブラリ名」 という関数を実行するため,今回は Init_greeter とする.
// * 関数の型は常に void
// * rb_define_module でモジュール名を定める
//   * Ruby ではモジュール名の先頭は大文字にするので Greeter とする (一方で,ファイル名などは全部小文字で良いが).
// * rb_define_module_function でメソッド名と,メソッド名に対応するラッパー関数名を登録する.
void Init_greeter(){
  VALUE module = rb_define_module("Greeter");
  rb_define_module_function(module, "hello", ruby_hello, 0);
}

この hello1.c プログラムをコンパイルする.Ruby には拡張ライブラリをコンパイルするための Makefile を作る仕組みがあるので,それを利用する. そのために,まず,必要なソースコードを 1 つのディレクトリにまとめ, 同じディレクトリに extconf.rb という名前のファイルを作る.

extconf.rb を以下のように書く.この extconf.rb では greeter という名前で拡張ライブラリを作るための Makefile を作成するように指示している.

require "mkmf"
create_makefile("greeter")

次に,Makefileを作るために extconf.rb を実行し,さらに make する.

$ ruby extconf.rb
$ make
$ ls
   Makefile  extconf.rb  greeter.so  hello1.c  hello1.o

extconf.rb 内で greeter を作るよう指定しているので make した結果として greeter.so が作成される. これを Ruby から実行してみる. main1.rb を以下のように書く.

require "./greeter"   #greeter.so を使うために
Greeter.hello         #インスタンスを作らずにメソッドを実行可

main1.rb を実行する.

$ ruby main1.rb
  Hello, world!   (<-- 無事に出力された)

拡張ライブラリ化 : クラス版

"Hello World" を出力するプログラムを作成する. ここでは C 言語で Greeter という名のクラスを定義し, そこに hello というメソッドを加えることにする. hello メソッドの中身を前述の hello.c と同様な内容とする.

$ mkdir -p ~/ruby-extlib/03
$ cd ~/ruby-extlib/03

C 言語でクラスを定義する場合は,以下のようにプログラム (hello2.c) を作成する.

#include "ruby.h"   // ruby.h のインクルードは必須

/* C 言語での "Hello World"*/
void c_hello(){
  printf("Hello, world!\n");
}

//ラッパー関数
// * 関数の型は常に VALUE
// * 関数の引数の型は常に VALUE.第一引数の名前は self にしておくこと.
// * 今は特に何も戻り値が必要でないので,Rubyの「nil」に 対応する「Qnil」という定数を与えている.
VALUE ruby_hello(VALUE self){
  c_hello();
  return Qnil;
}

//ライブラリの初期化関数
// * Ruby はライブラリを読み込んだらまず 「Init_ライブラリ名」 という関数を実行するため,今回は Init_greeter とする. 
// * 関数の型は常に void
// * rb_define_class でクラス名を定める
//   * Ruby ではクラス名の先頭は大文字にするので Greeter とする (一方で,ファイル名などは全部小文字で良いが).
// * rb_define_method でメソッド名と,メソッド名に対応するラッパー関数名を登録する.
void Init_greeter(){
  VALUE class = rb_define_class("Greeter", rb_cObject);
  rb_define_method(class, "hello", ruby_hello, 0);
}

この hello2.c プログラムをコンパイルする.Ruby には拡張ライブラリをコンパイルするための Makefile を作る仕組みがあるので,それを利用する. そのために,まず,必要なソースコードを 1 つのディレクトリにまとめ, 同じディレクトリに extconf.rb という名前のファイルを作る.

extconf.rb のを以下のように書く.この extconf.rb の中身は前節の内容からの変更はなく, greeter という名前で拡張ライブラリを作るための Makefile を作成するように指示している.

require "mkmf"
create_makefile("greeter")

次に,Makefileを作るために extconf.rb を実行し,さらに make する.

$ ruby extconf.rb
$ make
$ ls
  Makefile  extconf.rb  greeter.so  hello2.c  hello2.o

extconf.rb 内で greeter を作るよう指定しているので make した結果として greeter.so が作成される. これを Ruby から実行してみる.main2.rb を以下のように書く.

require "./greeter"  #greeter.so を使うために
obj = Greeter.new    #モジュール版と異なり,最初にインスタンス obj を作成
obj.hello

main2.rb を実行する.

$ ruby main2.rb
  Hello, world!   (<-- 無事に出力された)

拡張ライブラリ作成の具体例: 数の足し算

拡張ライブラリ化 : モジュール版

2 つの数の足し算を行う C プログラムを Ruby の拡張ライブラリ化する.

$ mkdir -p ~/ruby-extlib/04
$ cd ~/ruby-extlib/04

ここでは,C 言語で Calc という名のモジュールを定義し, そこに add というメソッドを加えることにする. C 言語でモジュールを定義する場合は,以下のような プログラム (add.c) を作成する.

#include "ruby.h"  // ruby.h のインクルードは必須

//足し算プログラムの本体.
int add(int a, int b){
  return( a + b );
}

// ラッパー関数
// * 関数の型は常に VALUE
// * 関数の引数の型は常に VALUE.第一引数の名前は self にしておくこと.
// * データの変換が行われていることに注意.Ruby では数値を含めすべてのものがオブジェクトとして扱われており,
//   個々のオブジェクトは C の構造体として実装されている.そのため,C 言語で書いたライブラリと数値の
//   やりとりをするには,構造体から数値を取り出したり,数値を構造体に入れる必要がある.整数型や浮動小数点型は
//   簡単に相互変換するマクロ、関数があるので,それを利用する.
VALUE wrap_add(VALUE self, VALUE aa, VALUE bb){
  int a, b, result;

  a = FIX2INT(aa);
  b = FIX2INT(bb);
  result = add(a,b);
  return INT2FIX(result);
}

// ライブラリの初期化関数
// * Ruby はライブラリを読み込んだらまず 「Init_ライブラリ名」 という関数を実行するため,今回は Init_calc とする.
// * 関数の型は常に void
// * rb_define_module でモジュール名を定める
//   * Ruby ではモジュール名の先頭は大文字にするので Calc とする (一方で,ファイル名などは全部小文字で良いが).
// * rb_define_module_function でメソッド名と,メソッド名に対応するラッパー関数名を登録する.
void Init_calc(){
  VALUE module = rb_define_module("Calc");
  rb_define_module_function(module, "add", wrap_add, 2);
}

この add.c プログラムをコンパイルする.Ruby には拡張ライブラリをコンパイルするための Makefile を作る仕組みがあるので,それを利用する. そのために,まず,必要なソースコードを 1 つのディレクトリにまとめ, 同じディレクトリに extconf.rb という名前のファイルを作る.

require "mkmf"
create_makefile("calc")

この extconf.rb では Calc という名前で拡張ライブラリを作るための Makefile を作成するように指示している. 次に,Makefileを作るために extconf.rb を実行し,さらに make する.extconf.rb 内で calc を作るよう指定しているので,make した結果として calc.so が作成される.

$ ruby extconf.rb
$ make
$ ls
  Makefile  add.c  add.o  calc.so  extconf.rb

extconf.rb 内で calc を作るよう指定しているので make した結果として calc.so が作成される. これを Ruby から実行してみる.main3.rb を以下のように書く.

require "./calc"  #calc.so を使うために
a = 1
b = 2
sum = Calc.add(a, b)
puts "#{a} + #{b} = #{sum}"

main3.rb を実行する.

$ ruby main3.rb
  1 + 2 = 3         (<-- 無事に出力された)

拡張ライブラリ化 : クラス版

2 つの数の足し算を行う C プログラムを Ruby の拡張ライブラリ化する.

$ mkdir -p ~/ruby-extlib/05
$ cd ~/ruby-extlib/05

ここでは,C 言語で Calc という名のモジュールを定義し, そこに add というメソッドを加えることにする. C 言語でモジュールを定義する場合は,以下のような プログラム (add.c) を作成する.

#include "ruby.h"  // ruby.h のインクルードは必須

// ラッパー関数
// * 関数の型は常に VALUE
// * 関数の引数の型は常に VALUE.第一引数の名前は self にしておくこと.
// * データの変換が行われていることに注意.Ruby では数値を含めすべてのものがオブジェクトとして扱われており,
//   個々のオブジェクトは C の構造体として実装されている.そのため,C 言語で書いたライブラリと数値の
//   やりとりをするには,構造体から数値を取り出したり,数値を構造体に入れる必要がある.整数型や浮動小数点型は
//   簡単に相互変換するマクロ、関数があるので,それを利用する.
VALUE wrap_add(VALUE self, VALUE aa, VALUE bb){
  int a, b, result;

  a = FIX2INT(aa);
  b = FIX2INT(bb);
  result = a + b ; //直接計算
  return INT2FIX(result);
}

// ライブラリの初期化関数
// * Ruby はライブラリを読み込んだらまず 「Init_ライブラリ名」 という関数を実行するため,今回は Init_calc とする.
// * 関数の型は常に void
// * rb_define_module でモジュール名を定める
//   * Ruby ではモジュール名の先頭は大文字にするので Calc とする (一方で,ファイル名などは全部小文字で良いが).
// * rb_define_module_function でメソッド名と,メソッド名に対応するラッパー関数名を登録する.
void Init_calc(){
  VALUE class = rb_define_class("Calc", rb_cObject);
  rb_define_method(class, "add", wrap_add, 2);
}

この add.c プログラムをコンパイルする.Ruby には拡張ライブラリをコンパイルするための Makefile を作る仕組みがあるので,それを利用する. そのために,まず,必要なソースコードを 1 つのディレクトリにまとめ, 同じディレクトリに extconf.rb という名前のファイルを作る.

require "mkmf"
create_makefile("calc")

この extconf.rb では Calc という名前で拡張ライブラリを作るための Makefile を作成するように指示している. 次に,Makefileを作るために extconf.rb を実行し,さらに make する.extconf.rb 内で calc を作るよう指定しているので,make した結果として calc.so が作成される.

$ ruby extconf.rb
$ make
$ ls
  Makefile  add.c  add.o  calc.so  extconf.rb

extconf.rb 内で calc を作るよう指定しているので make した結果として calc.so が作成される. これを Ruby から実行してみる.main4.rb を以下のように書く.また,モジュール版と異なり,最初にインスタンス obj を作成している.

require "./calc"  #calc.so を使うために
a = 1
b = 2
obj = Calc.new
sum = obj.add(a, b)
puts "#{a} + #{b} = #{sum}"

main4.rb を実行する.

$ ruby main4.rb
  1 + 2 = 3         (<-- 無事に出力された)