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

[準備] C 蚀語で Ruby 拡匵ラむブラリの䜜成

Ruby のモゞュヌルやクラスを C 蚀語で曞くこずができる 高速に凊理したい郚分を C 蚀語で実装するこずは良くあるこずである

前準備 (Linux の堎合)

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

前準備 (Windows11 の堎合)

Ruby の開発キット (DevKit) 付きパッケヌゞをむンストヌルする必芁があるこの開発キットに mruby.h だけでなくコンパむルに䜿う gcc や make などが含たれおいるむンストヌルには䞻に 2 ぀のやり方がある

  • RubyInstaller for Windows の Web (https://rubyinstaller.org/downloads/) から "Ruby + DevKit" ず曞かれたむンストヌラヌをダりンロヌドしお利甚する

  • PowerShell 䞊で winget コマンドで "Ruby x.x with MSYS2" ず曞かれたパッケヌゞをむンストヌルする

以䞋では winget を利甚するケヌスに぀いお詳説するたずパッケヌゞの ID を確認する"with MSYS2" の ID 列を控える

> winget search ruby
名前                ID                                    バヌゞョン  䞀臎          ゜ヌス
---------------------------------------------------------------------------------------
(...略...)
Ruby 3.4            RubyInstallerTeam.Ruby.3.4              3.4.9-1     Command: ruby winget
Ruby 4.0            RubyInstallerTeam.Ruby.4.0              4.0.5-1     Command: ruby winget
(...略...)
Ruby 3.4 with MSYS2 RubyInstallerTeam.RubyWithDevKit.3.4    3.4.9-1     Command: ruby winget
Ruby 4.0 with MSYS2 RubyInstallerTeam.RubyWithDevKit.4.0    4.0.5-1     Command: ruby winget
(...略...)

次にID を指定する圢で winget コマンドでむンストヌルする

> winget install --id RubyInstallerTeam.RubyWithDevKit.4.0 

この開発環境には GNU make など含たれおおりruby.h を呌ぶ堎合などは単に make ず打぀のではなく

$ ridk exec make

ずしないずいけないそうしないずパスが狂っおコンパむルに倱敗する

補足既に Ruby パッケヌゞが入っおいる堎合

基本的に最埌にむンストヌルしたパッケヌゞが利甚される耇数むンストヌルされおいる堎合は 以䞋のように珟圚利甚しおいる Ruby のむンストヌル堎所を調べおみるず良い

> where.exe ruby
> ruby -e "puts RbConfig::TOPDIR"

たた既に入っおいる Ruby パッケヌゞの実䜓を調べるには以䞋のコマンドで把握できる

> winget list --query Ruby

この winget list --query Ruby コマンドで Ruby が衚瀺されるがパッケヌゞ名に "withDevKit" が付いおいない堎合は開発キットのむンストヌルが必芁ずなる 珟圚のパッケヌゞを winget uninstall --id RubyInstallerTeam.Ruby.4.0 のように削陀しお入れ盎しおも良いが 以䞋のコマンドで DevKitMSYS2の远加むンストヌルをするのが簡単である

> ridk install

利甚する関数

モゞュヌルずクラスは䞀芋䌌おいるがクラスがデヌタず凊理を䞀぀にたずめたものに察しおモゞュヌルは凊理だけをたずめたものず蚀える具䜓的には以䞋のような違いがある

  • モゞュヌルはむンスタンスを䜜れない
  • モゞュヌルは継承できない

モゞュヌルを定矩する堎合

モゞュヌルを䜜成するには以䞋の関数を甚いる

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   (Windows11 の堎合はridk exec 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   (Windows11 の堎合はridk exec 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   (Windows11 の堎合はridk exec 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   (Windows11 の堎合はridk exec 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         (<-- 無事に出力された)