RubyでC言語拡張のやりかた入門してみる

link

The Ruby C API

#

【超入門】キミにも作れる! Ruby拡張ライブラリ開発 - Qiita

class FizzBuzz
    def fizzbuzz(arg)
        arg.each do |t|
            if t % 5 ==0 && t % 3 == 0
                puts "fizzbuzz"
            elsif t % 5 == 0
                puts "fizz"
            elsif t % 3 == 0
                puts "buzz"
            else
                puts t
            end
        end
    end
end

sample = FizzBuzz.new()
sample.fizzbuzz(1..20)


  • ふむ・・・・
  • むずかしい


void
Init_nyan()
{
    /* nyanの初期化処理 */
}
  • 初期化処理はたいてい
    • クラスを定義する
    • クラスにメソッドを定義する
  • とかそういった感じになる


  • Cの世界でRubyのオブジェクトはVALUE型で表されてる


  • 下記サンプルは動いた
#include "ruby.h"


VALUE output_printnumber(VALUE self, int argc){
    int i;

    for(i=0; i < argc; ++i) {
        printf("number is %d \n", i);
    }

    return 0;
}

// entry point
void Init_printnumber(void)
{
    VALUE cPrintnumber = rb_define_class("Printnumber", rb_cObject);
    rb_define_method(cPrintnumber, "outputnumber", output_printnumber, 1);
}
  • なるほど・・・
  • なんとなくわかってきたのでいろいろ調べる
  • mkmf
  • 書いていく順番
  • 第4章 クラスとモジュール
  • まずは、エントリーポイントとなる void Init_functionName() を書いていく, 例えば
    • function名は hogehoge
    • class名は Hogehoge
// entry point
void Init_hogehoge(){
    
    /*
        VALUE型, Rubyのデータとして使用するために使う
        cHogehoge: 名前はなんでもいいが、たぶん通例として小文字のcをつけたあとに、大文字function名で定義するのがよくあるかたち?
        rb_define_class: C言語拡張してRubyにClass定義するための関数, たぶんruby.h で定義されてる
        "Hogehoge": 定義したいクラス名、クラス名なので大文字からはじまる
        rb_cObject: オブジェクトクラスの派生オブジェクトとして作る、という宣言?
    */
    VALUE cHogehoge = rb_define_class("Hogehoge", rb_cObject);

    /*
        rb_define_method: methodを定義しますよ、って宣言で中の引数に必要事項を記述していく
        第一引数 cHogehoge: これは上の VALUE cHogehoge で定義した cHogehoge のこと, この定義したHogehogeクラスのデータに対してmethodを定義しますよ、って意味?
        第二引数 "hogehoge": string型で定義する、methodの名前, なのでhogehogeとかでなくfugafugaとか好きなやつでなんでもよい、このstringがRubyにいったあと使用されるmethod名になる
        第三引数 output_hogehoge: まだ定義してないが、Cコードの中に定義する関数(データ型の処理?)の名前, つまりoutput_hogehogeというのを後ほど定義する, なので名前はその定義と合わせておけばなんでもよい
        第四引数 1: methodが取る引数の数、intで定義する?
    */
    rb_define_method(cHogehoge, "hogehoge", output_hogehoge, 1);
}


/*
    VALUE: VALUE型, CとRubyでデータの受け渡しをするのに必要となる
    output_printnumber: データ、これが拡張したい関数の中身となる
        VALUE self: 先頭に与えられる引数, おまじないみたいなものか・・・?
        int argc: int型の引数, ここはVALUE argcとかでもいいのかもしれないが、その場合はコードがちょっとかわる --> C言語の型で定義するか、Rubyの型として変換してやるかでなんかかわっていくっぽい
*/
VALUE output_hogehoge(VALUE self, int argc){

    /*
        あとは中身をC言語で記述してやればよい
        例としてfor文で1から10までをprintfする処理を記述
    */
    int i;

    for(i=0; i < argc; ++i) {
        printf("number is %d \n", i);
    }

    // C言語なので一応最後にreturn 0 が必要? 無いとエラーになる(はず)
    return 0;
}


  • あとはextconf.rbを書いて、Makefileを出して、makeしてコンパイルが正常におわれば良い
  • あれ、試しに下記Rubyスクリプトを動作させると出力が思ったのと違う結果になる
#encoding:utf-8

require './printnumber'
Printnumber.new.outputnumber(10)
#=> 0から20までが出力される, なぜか10で止まらない?


#include "ruby.h"

// 引数もそのままでも動作はするが、一応 int argcではなく VALUE argcにしてやる
VALUE output_printnumber(VALUE self, VALUE argc){
    int i;

    // NUM2INT(argc) としてやることで、VALUEをCのデータに変換してやる
    for(i=0; i < NUM2INT(argc); i++) {
        printf("number is %d \n", i);
    }

    return 0;
}

// entry point
void Init_printnumber(void)
{
    VALUE cPrintnumber = rb_define_class("Printnumber", rb_cObject);
    rb_define_method(cPrintnumber, "outputnumber", output_printnumber, 1);
}
  • ひとまずこれで動いた、このようにしてCとRubyを行き来するのにいろいろAPIやマクロなどの力を借りて構築していくみたい