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 (<-- 無事に出力された)