涼風コンピュータblog

涼風 ・Rubyist, RubyやRuby on Railsに詳しくなっていきたいです

CSSの勉強(1)

本日はHTMLの課題とドットインストールのCSS入門CSSの勉強をしました

自分は長くJavaをやって来ました。C触ったり、Ruby触ったりしました。 プログラムは逐次実行、if分、forループなどの文法は似通っているため、 新しい言語を学ぶときにはそれが活かされるんですが、さすがにObjective-Cの引数にラベルがついているのには戸惑いました。 Objective-Cオブジェクト指向プログラミングなので、他のプログラミング言語と文法は 違いますが、やっていくうちにわかってきます。

CSSの文法も見慣れない文法なので、すぐに頭に入ってきませんが、だんだん使っていくうちに 慣れようと思います

CSSの基本は以下のとおりです。設定するプロパティが複数ある場合は(;)で区切ります

セレクタ {
  プロパティ : 値;
  プロパティ : 値;
  プロパティ : 値;
}

セレクタは以下の3つがあります

  • 要素: h1, p など
  • id: #sub
  • class: .item

色の指定はプロパティにcolorを設定し、値に色を設定します

h1 { color: red; } /* 固有名詞で指定 */
h1 { color: rgb(255, 0, 0); }
h1 { color: #f00; }     /* 16進数3桁 */
h1 { color: #ff0000; }  /* 16進数6桁 */ 

本日はドットインストールの#13まで行いました。明日も引き続きCSSの勉強をします

HTMLの勉強(1)

RailsでWebアプリケーションが作れるようになることが目標です

本日はHTMLの勉強をしました。主にドットインストールのHTML入門 で勉強しました

十何年前にJavaでWebアプリケーションを作るところでやりましたが、Webアプリの基本なのでHTMLをやることにしました。

最近はHTML5が使われるようになり、HTMLは文章の構造を記述し、見た目はCSSで行います。 色を指定を行うFontタグや中央にするcenterタグなどは廃止されており、これらは見た目なのでCSSで行います

ブラウザはMacGoogle Chromeで確認しました。バージョンは33.0.1750.152です

太文字にするには、strongタグありましたが、太くなりませんでした。太くするにはbタグを使用すると太くなりました

strongタグが使えるかどうかを以下のページ(http://caniuse.com/)で確認しましたが、使用できるブラウザはヒットしませんでした

インターネットで調べると、SEO対策でwebクローラーにはstrongタグをつけると意味的に強調になると書いてありましたが、本当かどうか、調べようがありませんでした

タグは省略されていて、覚えにくいものがありましたが、ドットインストールでは省略前の単語について、説明されており、覚える助けになりました。

  • ulタグ (unorder list)
  • olタグ (order list)

tableタグで、ヘッダ部分をグループ化するタグ、内容部分をグループ化するタグを知りました。ググると、フッター部分をグループ化するもあるようです

リンクを別のタブで開く時には、target属性に_blankを指定します

<a href="http://www.yahoo.co.jp" target属性に="_blank">Yahoo!</a>

labelタグを知りました。ラジオボタンで、ラジオボタンだけでなく、labelタグで囲まれた部分をクリックしてもラジオボタンをクリックしたことになります。ユーザービリティーが上がっています

divタグ、span要素はいままで使ってなかったので勉強になった

  • divタグ (汎用ブロック要素) 改行を含む
  • spanタグ (汎用インライン要素) 改行を含まない

HTML5では各ブロックに意味をつけるようになった。

  • header
  • footer
  • nav
  • article
  • aside
  • section

スクリプトタグは実行中にとまってしまうので、bodyタグの閉じタグの上に記述するのが一般的だそうです

明日は課題のHTMLをやりたいと思います

Rubyでツイキャスからコメントを取得してみた

はじめに

2014/01/01からひとりFluentdハッカソンをやっていました。インプットかアウトプットプラグインをなにしようかと思ったんですが、まず、ゲームの生放送するTwitch APIからチャットコメントを取得しようと思いました。ですが、1日のおわりにコメントが取得できないことに、やっと気づきいて、振り出しにもどりました。本日、目をつけたのがiPhone, Androidから生放送するツイキャス(TwitCasting)です。

そこでRubyからツイキャスからコメントを取得するGem TwitCasting-rbを作りました。

インストール

$ git clone https://github.com/suzukaze/Twicasting-rb
$ cd Twicasting-rb
$ rake build
$ gem install pkg/twicasting-x.x.x.gem
$ rbenv rehash # if you use rbenv.

ツイキャスからコメントを取得する

twitcasting.rbに以下の内容を保存してください。

require 'twitcasting'

# コメントを取得する
twitcast = TwitCasting::Client.new
comments = twitcast.get_comments({:movieid => '31846367'})

# コメントを表示する
puts "-" * 40
puts "date                 user   message"
puts "-" * 40
comments.each do |comment|
  time = Time.at(comment.created).strftime("%Y/%m/%d %H:%M:%S")
  puts "#{time}, #{comment.userid}, #{comment.message}"
end

コンソールから以下のコマンドを実行してください。:movieidが'31846367'の日付、ユーザーID、コメントを取得します。

$ ruby twicasting.rb
----------------------------------------
date                 user   message
----------------------------------------
2014/01/02 14:11:21, [user1], こういう道は吹き矢に気をつけてねw
2014/01/02 14:09:31, [user2], この辺も吹き矢に気をつけてねw
2014/01/02 12:31:03, [user3], 今日やってるバイク屋少なそう・・・
2014/01/02 12:26:25, [user4], 普通ボルト外れないぞ
2014/01/02 12:25:46, [user5], e
2014/01/02 12:14:46, [user6], もい
2014/01/02 12:14:44, [user7], 待機
2014/01/02 12:14:32, [user8], 食事中.....
2014/01/02 12:14:24, [user9], 来た道を戻って、一号線へ行って下さい!
----------------------------------------

TwitCasting::Client#get_comments({:user => 'user', :movieid => 'movieid'})はuserに指定したユーザーID, movieidは指定したムービーIDのコメントを取得します。

おわりに

TwitCasting APIのJSONを返すAPIはまだあるので、今後実装していきたいです。本来の目的はFluentdのインプットプラグインをつくることなので、次はツイキャスのコメントを取得するFluentdプラグインに取りかかりたいです。

ServerEngineを動かしてみた

はじめに

Server EngineはUnicornのような強固なマルチプロセスサーバーを実装するためのフレームワークで、rubyで記述されています。ruby 1.9.3, 2.0.0で動作します。ログイベント収集ミドルウェア Fluentd v11に使用されています。

メイン機能は以下のとおりです。 https://github.com/frsyuki/serverengine#serverengine

Supervisorは設定ファイルを読み込み、Serverを起動します。Serverは作業する複数のWorkを起動します SupervisorはWorkerにpipe経由でハートビートを送り、自動リスタートさせます

他の機能:

  • ロギンングとログローテーション
  • シグナルハンドラー
  • シグナル時にスタックトレースとヒープダンプ
  • user変更, group変更 および umask変更
  • プロセス名の変更

Server Engineをインストール

Server Engineをインストールします

$ gem install serverengine

Simplest serverを実行

https://github.com/frsyuki/serverengine#simplest-server

Workerを1つ起動し、ログファイルに"Awesome work!"ログを書き込みます。デーモンで起動されますので、終了させるまでバックグラウンドで実行されます。筆者はMac 0S X 10.8.4、ruby ruby 2.0.0p0で確認しました。simple-server.rbとしてコードを保存します

$ ruby simple-server.rb

ファイルが2つ作成されますので、確認してみましょう。

$ ls
myserver.log        myserver.pid        simplest-server.rb

simple-server.rbに以下が設定されているため、ログファイルとしてmyserver.log,起動したプロセスIDをmyserver.pidに記録されます

se = ServerEngine.create(nil, MyWorker, {
  :daemonize => true,
  :log => 'myserver.log',
  :pid_path => 'myserver.pid',
})

myserver.logがログを書き出し先のファイルです。 myserver.pidが起動しているプロセスIDです

$ tail -f myserver.log 
I, [2013-12-22T21:52:32.750339 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:33.753536 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:34.754886 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:35.756227 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:36.756767 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:37.758424 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:38.759863 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:39.761215 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:40.762543 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:41.763787 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:42.765654 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:43.766261 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:44.767841 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:45.768601 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:46.769669 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:47.770607 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:48.771009 #12873]  INFO -- : Awesome work!
I, [2013-12-22T21:52:49.771709 #12873]  INFO -- : Awesome work!

1秒ごとにログが増えていることがわかります

myserver.pidファイルで起動しているプロセスIDを確認します

$ cat myserver.pid
12873

psコマンドでプロセスの生存を確認します

$ ps 12873
  PID   TT  STAT      TIME COMMAND
12873   ??  S      0:00.20 ruby simplest-server.rb

それでは、プログラムを終了してみましょう。TERMシグナルを送り、プログラムを止めます

$ kill -TERM 12873

myserver.logに@stop=trueが表示されました。これはTERMシグナルによりMyWoker#stopメソッドが呼ばれ、@stop=trueが設定され、MyWoker#runメソッドのループが終了したためです

...
I, [2013-12-22T22:46:11.712393 #13510]  INFO -- : Awesome work!
I, [2013-12-22T22:46:12.713562 #13510]  INFO -- : Awesome work!
I, [2013-12-22T22:46:13.714934 #13510]  INFO -- : Awesome work!
I, [2013-12-22T22:46:14.715912 #13510]  INFO -- : Awesome work!
I, [2013-12-22T22:46:15.717315 #13510]  INFO -- : @stop=true

psコマンドでプロセスが消えていることを確認しましょう

$ ps 12873
  PID   TT  STAT      TIME COMMAND

おわりに

今回はWorkerが1つというものすごく単純な構成をみました。Workerにデーモンで作業させて、TERMシグナルによる終了をみました。本当は、次のサーバー1つと4つのWokerのmulti-tcp-serverもやりたかったのですが、うまく動作させることができませんでした。たぶん、Workerに送信するクライアントプログラムが必要になると思います。それは次回やりたいと思います

mruby-jsonとmruby-msgpackの速度比較してみた!

はじめに

JSONとMessagePackでは、シリアライズとデシリアライズにおいて、MessagePackがバイナリフォーマットなので、数倍速いです。今回はmruby版でシリアライズとデシリアライズの速度を比べてみようと思います。 ちなみにシリアライズはデータをファイルに保存したり、ネットワークで送受信できるようにすることです。反対に、デシリアライズとはシリアライズしたファイルやネットワーク上のデータをプログラムで扱えるように復元することです。

ビルド

1. build_config.rbにmruby-jsonとmruby-msgpackのconfig.gem行を追加:

MRuby::Build.new do |conf|

  # ...(省略)...
  conf.gem :git => 'https://github.com/mattn/mruby-json'
  conf.gem :git => 'https://github.com/suzukaze/mruby-msgpack.git'
end

2.コマンドプロプトからビルド:

ruby ./minirake

ベンチマークプログラム

自分はmacを使用しているので、計測したいmrubyファイルをmrubyで実行して、timeコマンドで計測すればよいです。 time mruby mruby-benchmark.rb

ですが、いくつも計測するのに汎用的なベンチマークプログラムはないかと探しましたら、rubyの標準にBenchmarkモジュールがありました。 よさそうと思ったのですが、すべて理解するのは大変だったので、少し参考にしてmruby用に自作しました。

SimpleBenchmarkクラス

class SimpleBenchmark

  def initialize(width = 0)
    @width = width
  end

  def measure(label)
    start = Time.now
    yield if block_given?
    passed = Time.now - start

    puts "#{make_fixed_label(label)}passed time #{passed} sec"
  end

  def make_fixed_label(label)
   if @width - label.length > 0
      label + ' ' * (@width - label.length)
    else
      label
    end
  end

end

次のハッシュをシリアライズとデシリアライズをJSONとmsgpackで10000回繰り返します。

{'name' => 'suzukaze', 'level' => 15})

計測プログラムは次のようになります:

benchmark = SimpleBenchmark.new("MessagePack:".length)

n = 10000

#JSONでシリアライズとデシリアライズする時間を計測する
benchmark.measure("JSON:") do
  n.times{ JSON.parse(JSON.generate({'name' => 'suzukaze', 'level' => 15})) }
end
#MessagePackでシリアライズとデシリアライズする時間を計測する
benchmark.measure("MessagePack:") do
 n.times{ MessagePack.unpack(MessagePack.pack({'name' => 'suzukaze', 'level' => 15})) }
end

計測環境

OS:Mac OS X 10.8.4

プロセッサ: 2.13GHz intel Core 2 Duo

計測

次のコマンドをコマンドプロンプトから実行します:

mruby mruby-msgpack-profile.rb

mruby-msgpack-profile.rbは最後に全部、掲載しています。

計測結果

JSON:       passed time 0.543839 sec
MessagePack:passed time 0.092397 sec

計測結果はMessagePackがJSONの約5.8倍の速さとなりました。

最後に

計測に使ったソースの全文を掲載します。

mruby-msgpack-profile.rb

class SimpleBenchmark

  def initialize(width = 0)
    @width = width
  end

  def measure(label)
    start = Time.now
    yield if block_given?
    passed = Time.now - start

    puts "#{make_fixed_label(label)}passed time #{passed} sec"
  end

  def make_fixed_label(label)
   if @width - label.length > 0
      label + ' ' * (@width - label.length)
    else
      label
    end
  end

end

benchmark = SimpleBenchmark.new("MessagePack:".length)

n = 10000

#JSONでシリアライズとデシリアライズする時間を計測する
benchmark.measure("JSON:") do
  n.times{ JSON.parse(JSON.generate({'name' => 'suzukaze', 'level' => 15})) }
end
#MessagePackでシリアライズとデシリアライズする時間を計測する
benchmark.measure("MessagePack:") do
  n.times{ MessagePack.unpack(MessagePack.pack({'name' => 'suzukaze', 'level' => 15})) }
end

mrubyでMessagePackを作った!

MessagePackって何?

MessagePackとはなにかといいますと、JSON互換の異なる言語間でデータ交換するためのシリアライズフォーマットです。JSONのような型を扱え、 JSONよりコンパクトで、速いと言われています。(mrubyではまだ性能評価していないので、今後やってみたいと思います。) ログコレクターで有名なfluentdというミドルウェアで使用されています。

MessagePack for mruby

今回、MessagePack for ruby v 0.5.5からmrubyにGEMとして移植しました。すべて移植したわけではないので、α版というところでしょうか。 MessagePack#pack,MessagePack#unpackメソッドでだいたいの型を使用出来るようになっています。

次の例は配列をシリアライズして、MessagePack#unpackメソッドでデシリアライズで元に戻しています。

msg = MessagePack.pack([1, 2, 3])  #=> "\x93\x01\x02\x03"
MessagePack.unpack(msg)            #=> [1, 2, 3]

または

msg = [1, 2, 3].to_msgpack   #=> "\x93\x01\x02\x03"
MessagePack.unpack(msg)      #=> [1, 2, 3]

このようなことがmrubyでできるようになります。

それではまずMessagePack for mruby(以下mruby-msgpack)をビルドしてみましょう

ビルド

1. コマンドプロンプトでmruby-msgpackをダウンロードします:

git clone https://github.com/suzukaze/mruby-msgpack.git

2. mrubyのルートディレクトリにあるbuild_config.rbconf.gem行追加します:

MRuby::Build.new do |conf|

  # ...(省略)...
  conf.gem :git => 'https://github.com/suzukaze/mruby-msgpack.git'
end

3. テストコードを実行して、プログラムが正常かを確認します:

rake test

または、rakeがなければ

./minirake test

テスト結果を確認し、エラーがなければOKです(KOやCrashの値が0):

Total: 682
   OK: 682
   KO: 0
Crash: 0
 Time: 0.068817 seconds

4. ビルドします

rake

または

./minirake

mruby-msgpackを試す

mruby-msgpackをさっそく試してみましょう。まず、コマンドプロプトから:

mruby -e 'msg = MessagePack.pack([1, 2, 3]); puts MessagePack.unpack(msg)'

[1, 2, 3]とコマンドプロンプトに表示されたでしょうか。 MessagePackモジュールにあるpackとunpackメソッドを使用して 要素3つ持った配列をシリアライズ後に、デシリアライズして元に戻しています。 今回は[1, 2, 3]をシリアライズ後にでデシリアライズしましたので、結果が[1, 2, 3]になります。

また、シリアライズは次のように配列に対してto_msgpackメソッドを呼ぶことで同じことができます。

mruby -e 'msg = [1, 2, 3].to_msgpack; puts MessagePack.unpack(msg)'

これはArrayクラスを拡張してシリアライズするto_msgpackメソッドを使用出来るようにしています。

mruby-msgpackで扱える型

ではここで、mruby-msgpackで扱える型について紹介しましょう。

  • nil
  • true
  • false
  • Fixnum
  • Float
  • String
  • Symbol (Stringに変換されます)
  • Array
  • Hash (mrubyのHashは順番が保証されません。ruby 1.9からHashが順序付けに)

内部ではさらに、データの長さによって同じ型でも、フォーマットが異なります。 そこは省略しますが、短いデータには短い型情報のビット数、大きなデータには大きい型情報のビット数が割り当てられています。

rubyのMessagePackと異なる点は、mrubyにはBignumがありません。そのためmrubyではFixnumで収まらない大きな値はFloatに自動的に変換されます。これはmrubyの仕様です。Floatも扱えるのですが、大きな値でrubyと比べて値が異なることがあったのでここは要検証する必要があると思っています。

いろいろ試す

では、いろいろ試してみましょう。

1. Fixnum (整数)

1

mruby -e 'msg = 1.to_msgpack; puts MessagePack.unpack(msg)'

-1

mruby -e 'msg = -1.to_msgpack; puts MessagePack.unpack(msg)'

256

mruby -e 'msg = 256.to_msgpack; puts MessagePack.unpack(msg)'

63535

mruby -e 'msg = 65535.to_msgpack; puts MessagePack.unpack(msg)'

2. Float (浮動小数点)

1.23

mruby -e 'msg = 1.23.to_msgpack; puts MessagePack.unpack(msg)'

-1.0

mruby -e 'msg = -1.0.to_msgpack; puts MessagePack.unpack(msg)'

3. String(文字列)

"abc"

mruby -e 'msg = "abc".to_msgpack; puts MessagePack.unpack(msg)'

4. Symbol(シンボル)

:abcというシンボルは"abc"になります

mruby -e 'msg = :abc.to_msgpack; puts MessagePack.unpack(msg)'

5. true

true

mruby -e 'msg = true.to_msgpack; puts MessagePack.unpack(msg)'

6. false

false

mruby -e 'msg = false.to_msgpack; puts MessagePack.unpack(msg)'

7. nil

nilも可能ですがnil.to_sは空文字なので、if文でnilだったら、"nil!"と表示しています

mruby -e 'msg = nil.to_msgpack; puts "nil!" if MessagePack.unpack(msg) == nil'

8. Array(配列)

[1, "a", true]

mruby -e 'msg = [1, "a", true].to_msgpack; puts MessagePack.unpack(msg)'

9. Hash(ハッシュ)

{"name" => "suzukaze", "level" => 10, "list" => [1, 2, 3]}

mruby -e 'msg = {"name" => "suzukaze", "level" => 10, "list" => [1, 2, 3]}.to_msgpack; puts MessagePack.unpack(msg)'
{"list"=>[1, 2, 3], "name"=>"suzukaze", "level"=>10} #順番は保証されない

最後に

mruby-msgpackはrubyにはあって、mrubyにない実装はまだありますが、ある程度使えるのではないかと思います。 ruby版には内部バッファを使用してコピー処理を抑えて高速に動作するクラスがあります。こちらも移植していきたいと思います。 なおこの実装にはIOクラス(実際にはStringIOが使用されていた)のでmruby-ioの実装にも貢献していけたならと思います。 mruby-msgpackのソースはここあります。よかったらのぞきにきてください。

mruby-fluent-loggerの作者の方がmruby-msgpackがないので代わりにmruby-jsonを使っているとのこと。mruby-msgpack作ったので、使ってください!!おかしかったら直します。

mrubyでC言語のGEMを作成する

rubyの方言に組み込み向け用のmrubyというものがあります。rubyには簡単にライブラリが組み込めます。mrubyにも便利なライブラリがあったらいいですよね。
mrubyはまだ若い言語ですが、GEMがあります。

rubyはGEMをあとからインストールできますが、mrubyのGEMはmruby本体と同時にビルドします。ではまず、mrubyをGitHubから取得してビルドしてみましょう。 

$ git clone https://github.com/mruby/mruby.git
$ cd murby$ ls
AUTHORS    	MITL		TODO		doc		mrblib
CONTRIBUTING.md	Makefile	benchmark	examples	src
ChangeLog	NEWS		bin		include		tasks
INSTALL		README.md	build		minirake	test
LEGAL		Rakefile	build_config.rb	mrbgems		tools

mrubyのソースを取得できたのでビルドします。

$ make
ruby ./minirake
(in /Users/xxxxxx/work/mruby/origin/mruby)
CC    tools/mruby/mruby.c -> build/host/tools/mruby/mruby.o
CC    src/array.c -> build/host/src/array.o
CC    src/class.c -> build/host/src/class.o
CC    src/codegen.c -> build/host/src/codegen.o
省略
Build summary:

================================================
      Config Name: host
 Output Directory: build/host
         Binaries: mruby, mrbc, mirb
================================================

エラーがでずに上記のようにbuild summaryが表示されたら、ビルド成功です。mrubyが動作するかどうか確かめてみましょう。

$ cd bin
$ ls
mirb    mrbc	mruby
$ ./mruby -e 'p 1+1'
2

-eオプションはワンライナーと言いまして、一行でmrubyのコードを実行するオプションです。1+1の結果が2と表示されました。

毎回binディレクトリで実行するのは面倒ですのでパスを通します。私はmac OS Xを使用していますので、~/.profileに以下のように記載しました。

export MRUBY=$HOME/mruby

ホームの直下のmrubyディレクトリがあるとします。

source ./profileで有効にします。

$ source ./profile
$ mruby -e 'p 1 + 2'
3

これでどこでもmrubyを実行出来るようになりました。

次は、ビルドの設定ファイルについて見てみましょう。

mrubyのルートに戻ります。

$ cd ..
$ cd murby$ ls
AUTHORS        MITL		TODO		doc		mrblib
CONTRIBUTING.md	Makefile	benchmark	examples	src
ChangeLog	NEWS		bin		include		tasks
INSTALL		README.md	build		minirake	test
LEGAL		Rakefile	build_config.rb	mrbgems		tools

build_config.rbがビルドの設定ファイルです。rubyで記述されています。

MRuby::Build.new do |conf|
  # load specific toolchain settings
  toolchain :gcc

  # Use mrbgems
  # conf.gem 'examples/mrbgems/ruby_extension_example'
  # conf.gem 'examples/mrbgems/c_extension_example' do |g|
  #   g.cc.flags << '-g' # append cflags in this gem
  # end
  # conf.gem 'examples/mrbgems/c_and_ruby_extension_example'
  # conf.gem :github => 'masuidrive/mrbgems-example', :branch => 'master'
  # conf.gem :git => 'git@github.com:masuidrive/mrbgems-example.git', :branch => 'master', :options => '-v'
  # 省略
  
end

GEMを作成するにはC言語rubyC言語rubyの3種類がありますが、ここはC言語で作成してみようと思います。インタフェースはrubyで実装はC言語で高速にすることがライブラリとしてよいのではないかと思ったからです。

 

すでにmruby/examples/mrbgems/c_extension_exampleディレクトリにサンプルがありますのでそれを使用します。config.gem のあとにGEMのパスを記述します。

MRuby::Build.new do |conf|
  # load specific toolchain settings
  toolchain :gcc

  # Use mrbgems
  conf.gem 'examples/mrbgems/c_extension_example'

別端末を起動し、サンプルディレクトリに移動します。

$ cd mruby/examples/mrbgems/c_extension_example
$ ls
README.md    mrbgem.rake	src		test

mrbgem.rakeのgemの情報を書き込みます。

spec.licenseがラインセンス、spec.authorsが著者権者になります。公開するときは書き換えましょう。


MRuby::Gem::Specification.new('c_extension_example') do |spec| 
  spec.license = 'MIT'
  spec.authors = 'mruby developers' 
end

srcがC言語のソースになります。

$ cd src

$ ls

example.c

 

example.cをテキストエディタで開きます。

#include <mruby.h>
#include <stdio.h>

static mrb_value
mrb_c_method(mrb_state *mrb, mrb_value self)
{
  puts("A C Extension");
  return self;
}

void
mrb_c_extension_example_gem_init(mrb_state* mrb) {
  struct RClass *class_cextension = mrb_define_module(mrb, "CExtension");
  mrb_define_class_method(mrb, class_cextension, "c_method", mrb_c_method, ARGS_NONE());
}

void
mrb_c_extension_example_gem_final(mrb_state* mrb) {
  // finalizer                                                                                             
}

まずはそのままサンプルをビルドしてみましょう。先ほど、ビルドした端末でビルドします。

$ make

エラーがでなければビルド成功です。

別端末を起動します。mrubyを実行できるように~/.profileを読み込みます。

$ source ~/.profile

適当な場所でgemを実行するファイルを作成します。

example.rb

# encoding : utf-8
# 引数なしメソッド
CExtension::c_method

それでは実行してみましょう。

$ mruby example.rb
A C Extension

CExtension::c_methodメソッドが実行され、A C Extensionと表示されました。
GEMのソースexample.cを見てみましょう。
CExtension::c_methodメソッドがよばれるとmrb_c_method関数が呼ばれます。

static mrb_value
mrb_c_method(mrb_state *mrb, mrb_value self)
{
  puts("A C Extension");
  return self;
}

初期化です。

void mrb_c_extension_example_gem_init(mrb_state* mrb) 
{
  struct RClass *class_cextension = mrb_define_module(mrb, "CExtension");
  mrb_define_class_method(mrb, class_cextension, "c_method", mrb_c_method, ARGS_NONE());
 }
 

mrb_define_class_methodの第2引数"c_method"がrubyでのメソッド名、第3引数mrb_c_methodが第2引数で指定されたメソッドが呼ばれたときに実行されるC言語の関数となります。

終了処理です。

void mrb_c_extension_example_gem_final(mrb_state* mrb)
{
   // finalizer
}

それでは、rubyからGEMを呼び出してみましょう。rubyのコードです。

example.rb

# encoding : utf-8
# 引数なしメソッド
CExtension::c_method

実行してみましょう。

$ ruby example.rb
A C Extension

mrb_c_method関数が実行されて、「A C Extension」と表示されました。 次は引数に整数、戻り値に整数を返すメソッドを実装してみましょう。

example.c

#include <stdio.h>
#include "mruby.h"
#include "mruby/variable.h"

static mrb_value
mrb_c_method(mrb_state *mrb, mrb_value self)
{
  puts("A C Extension");
  return self;
}

// インクリメントする
static mrb_value
mrb_inc(mrb_state *mrb, mrb_value self)
{
  mrb_int i;
  mrb_get_args(mrb, "i", &i);
  i++;
  return mrb_fixnum_value(i);
}

void
mrb_c_extension_example_gem_init(mrb_state* mrb) {
  struct RClass *class_cextension = mrb_define_module(mrb, "CExtension");
  mrb_define_class_method(mrb, class_cextension, "c_method", mrb_c_method, ARGS_NO\
NE());
  mrb_define_class_method(mrb, class_cextension, "inc", mrb_inc, ARGS_ANY());
}

void
mrb_c_extension_example_gem_final(mrb_state* mrb) {
  // finalizer                                                                     
}

example.cにC言語で整数をインクリメントするmrb_inc()関数を追加しました。mrb_c_extension_example_gem_init()関数に>mrb_define_class_method(mrb, class_cextension, "inc", mrb_inc, ARGS_ANY())を追加しました。第4引数に引数任意を指定しました。

example_inc.rb

# encoding : utf-8
# インクリメント
x = 2
puts "x=#{x}"
x = CExtension::inc(x)
puts "インクリメント"
puts "x=#{x}"

実行してみましょう。

$ mruby example_inc.rb
x=2
インクリメント
x=3

xの値が2から3にインクリメントされました。

1: static mrb_value
2: mrb_inc(mrb_state *mrb, mrb_value self)
3: {
4:  mrb_int i;
5:  mrb_get_args(mrb, "i", &i);
6:  i++;
7:  return mrb_fixnum_value(i);
8: }

1行目mrb_valueはmrubyの値を表す構造体です。

4行目はmruby内部のintを表します。

5行目でmrubyメソッドの第1引数をiに代入します。

6行目でインクリメントします。

7行目のmrb_fixnum_value関数でrubyの整数にとして戻り値を返します。

C言語のintとmrubyは同じではないので変換する必要があります。

他にfloatやtrue,false,nilをmrb_valueとして表す関数があります。

インスタンスメソッドや他の型も扱える方法を調べていきたいと思います。