mrb_valueについて調べてみた
昨日の続き。
mrubyのソースコードを読むと、mrb_value
という構造体がよく出てくるのでソースコードを追いかけて使い方を調べてみた。参照しているコミット番号は昨日と同じく「9663a7」です。
mrb_valueの定義
// include/mruby/value.h:40 typdef struct mrb_value { union { mrb_float f; void *p; mrb_int i; mrb_sym sym; } value; enum mrb_vtype tt; } mrb_value;
mrb_value
構造体は値とその値のデータ型をもつ。enum mrb_vtype
にはMRB_TT_FIXNUM
とかMRB_TT_STRING
などが入る。value
とtt
は適切な組み合わせにする必要があるはず。
mrb_value
はMRB_NAN_BOXING
が定義されているかどうかでその定義が変わるんだけど、MRB_NAN_BOXING
はmrbconf.hでコメントアウトされていたので、mrb_value
のデフォルトの定義は上のようになる。
// include/mrbconf.h:23 /* represent mrb_value in boxed double; conflict with MRB_USE_FLOAT */ //#define MRB_NAN_BOXING
どういうときにこれを使うのかはまだよくわかってない。
mrb_valueとデータ型の変換
int型、char型などとmrb_value
を変換する方法も調べた。まず、変換する関数によく使われているmrb_value
構造体に値をセットするマクロがある。
// include/mruby/value.h:53 #define MRB_SET_VALUE(o, ttt, attr, v) do {\ (o).tt = ttt;\ (o).attr = v;\ } while (0)
これを使って変換する関数が実装されているっぽい。とりあえず見つけたのは以下の通り。
int
-> mrb_value
// include/mruby/value.h:205 static inline mrb_value mrb_fixnum_value(mrb_int i) { mrb_value v; MRB_SET_VALUE(v, MRB_TT_FIXNUM, value.i, i); return v; }
mrb_value
-> int
// include/mruby/value.h:145 #define mrb_fixnum(o) (o).value.i
float
-> mrb_value
// include/mruby/value.h:58 static inline mrb_value mrb_float_value(mrb_float f) { mrb_value v; MRB_SET_VALUE(v, MRB_TT_FLOAT, value.f, f); return v; }
mrb_value
-> float
// include/mruby/value.h:51 #define mrb_float(o) (o).value.f
char[]
-> mrb_value
// src/string.c:670 char * mrb_string_value_ptr(mrb_state *mrb, mrb_value ptr) { mrb_value str = mrb_str_to_str(mrb, ptr); return RSTRING_PTR(str); }
char[]
-> mrb_value
// src/string.c:232 mrb_value mrb_str_new(mrb_state *mrb, const char *p, size_t len) { struct RString *s; s = str_new(mrb, p, len); return mrb_obj_value(s); }
mrubyで定義したクラスとメソッドをCから呼び出す
mrubyで書いた方がいいところはmrubyで書いてそうじゃないところはCで書く、という開発をするには、Cで定義した関数をRubyから実行させたり、逆にRubyで定義したクラスやメソッドをCから呼び出せるようにする必要があると思った。前者のような実装はmrbgemsを読めばたくさんある一方で、後者の実装は調べたけどあんまりなかった。そこで、先日「Head First C」でCの初歩を学んだことだし、mrubyのソースコードを読みながら後者の「mrubyで定義したクラスとメソッドをCから呼び出す」実装を試行錯誤してみた。
試行錯誤してみてとりあえず動いたというだけで、正しいやり方じゃないかもしれないので、コメントか@naoty_k宛にメッセージをいただけるとありがたいです。また、参照しているmrubyのコミット番号は「9663a7」です。
Rubyのクラスとメソッドを用意
適当にPersonクラスとメソッド2つを用意する。あとでこれらをCから呼び出す。
// person.rb class Person attr_accessor :name, :age def initialize(name, age) @name = name @age = age end def greeting "Hello, my name is #{name}, #{age} years old." end end
mrbcでコンパイル
RubyのファイルをCからロードするにはいくつか方法があるようだけど、今回はmrbcで*.mrb形式にコンパイルしてCからロードするようにする。
$ ls person.rb $ mrbc person.rb $ ls person.mrb person.rb
Cから定義したクラスとメソッドを呼び出す
CからRubyで定義したPerson
インスタンスを生成してgreeting
メソッドの結果を標準出力に表示してみる。
// greeting.c #include <stdio.h> #include <mruby.h> #include <mruby/string.h> int main() { mrb_state* mrb = mrb_open(); // mrubyファイルをロードする FILE *fd = fopen("person.mrb", "r"); mrb_load_irep_file(mrb, fd); // クラスオブジェクトを取得する struct RClass *person = mrb_class_obj_get(mrb, "Person"); // 引数をmrb_valueに変換する mrb_value person_value = mrb_obj_value(person); mrb_value name_value = mrb_str_new(mrb, "naoty", 5); mrb_value age_value = mrb_fixnum_value(25); // Person#newを呼び出す mrb_value naoty = mrb_funcall(mrb, person_value, "new", 2, name_value, age_value); // Person#greetingを呼び出す mrb_value greeting_value = mrb_funcall(mrb, naoty, "greeting", 0); // 返り値をchar*に変換して出力する char *greeting = mrb_string_value_ptr(mrb, greeting_value); printf("%s\n", greeting); mrb_close(mrb); return 0; }
- *.mrb形式のファイルをロードするには
mrb_load_irep_file()
を実行する。 - 次にクラスを取得するには
mrb_class_obj_get()
を実行し、メソッドを呼び出すにはmrb_funcall()
を実行する。 mrb_funcall()
には、第2引数にメソッドのレシーバ、第3引数にメソッド名、第4引数にメソッドの引数の数、第5引数以降にはメソッドの引数を渡す。第2引数と第5引数以降はint
やchar*
などをそのまま渡すことはできなくて、mrb_value
という構造体に変換する必要がある。変換するための関数については長くなりそうなので、別の記事にしようと思う。mrb_funcall()
の返り値もmrb_value
構造体なので、標準出力をするためにchar*
に変換する。
Cをコンパイルして実行
Cのソースコードをmrubyのヘッダーファイルやスタティックライブラリと一緒にコンパイルする。僕の環境だと以下のコマンドでコンパイルできた。
$ gcc -I ~/mruby/include greeting.c ~/mruby/build/host/lib/libmruby.a -lm -o greeting $ ./greeting Hello, my name is naoty, 25 years old.
greeting.cはperson.mrbに依存し、person.mrbはperson.rbに依存しているので、一連のビルドはMakefileかRakefileで自動化したほうがいいと思う。
// Rakefile require "rake/clean" CC = "gcc" MRBC = "mrbc" CLEAN.include("person.mrb") CLOBBER.include("greeting") task default: "greeting" file "greeting" => ["greeting.c", "person.mrb"] do |t| sh "#{CC} -I ~/mruby/include #{t.prerequisites[0]} ~/mruby/build/host/lib/libmruby.a -lm -o #{t.name}" end file "person.mrb" => ["person.rb"] do |t| sh "#{MRBC} #{t.prerequisites[0]}" end
$ rake $ ./greeting
参考
@naoty_k クラスの取り出しはmrb_class_obj_get()、メソッドの呼び出しはmrb_funcall()を使ってください。funcallには派生形あり。
— Yukihiro Matsumotoさん (@yukihiro_matz) 2013年4月30日
Head First Cの感想
以前参加したmrubyの勉強会でC言語でコードを書く機会があってよくわからなかったので、オススメされた「Head First C」を読んでみた。
- 作者: David Griffiths,Dawn Griffiths,中田秀基(監訳),木下哲也
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/04/03
- メディア: 大型本
- この商品を含むブログを見る
500ページ超あるんだけどすごくわかりやすくて10日間で読み終わった。Head FirstシリーズはRailsなどいろいろあるのは知ってたけど読んだことなかった。どうやらこのシリーズは科学的なアプローチで分かりやすく書いているみたいで、確かにわかりやすかった。C言語の入門書というとすごい古い本や固っくるしい本が多いような印象があるけど、この本は今月発売されたばかりでかつカジュアルだったので、とっかかりやすかった。
内容はmain()
とかポインタから始まって、最後はマルチプロセスのwebサーバーを作ったりマルチスレッドプログラミングを扱うところまでカバーしてる。やっぱりポインタが鬼門で、何度も読み返したけど80%くらいの理解。文字列ポインタとか配列変数とかで???ってなった。それ以外は本当にわかりやすくてよかった。特にメモリ管理のところはわかりやすかった。普段Rubyとか書いてるとあまりメモリのことは考えないけど、スタックとヒープの区別がついたり、valgrindを使ってメモリリークを調べたりすることでだいぶメモリに対する意識が高まった。また、gccでコンパイルしたりリンクをひとつひとつ丁寧にやることでmakeの有難みを感じた。iOSアプリを開発するとたまに出てくるスタティックリンクライブラリ(lib*.aみたいなの)の正体もわかってよかったし、今後iOSアプリのビルドに失敗しても怯えなくて済みそう。あと、途中のコラムでOpenCVについて簡単な説明があってちょっと興味がわいてきた。Raspberry Piとウェブカメラを買ってOpenCVで遊んでみたいと思った。
vimでTodoリスト
今までいろんなTodo管理アプリを試してきたけど、「GUIアプリほど高機能はいらない」「ターミナル上でtodoを確認したい」という理由でvimでTodoリストを書くようになった。これによるとGithubがGithub Flavored MarkdownにTodoリスト記法を実装したようなので、これに倣ってmarkdownでTodoリストを書くことにした。
todoコマンド
まず、Todoリストを開くコマンドをaliasで定義してみた。これでtodo
でTodoリストを確認できる。さらに、Dropbox上にファイルを置けば複数PCで共有できるので、オフィスのPCとプライベートPCでTodoリストを共用できる。
# .zshrc if [ -e "$HOME/Dropbox" ]; then alias todo="$EDITOR $HOME/Dropbox/.todo.md" else alias todo="$EDITOR $HOME/.todo.md" end
vimでmarkdownを書く準備
次に、vimでmarkdownを書く準備をする。普通に*.mdを開くとmodula2というfiletypeで認識されてしまい、markdownファイルとして見なされないので、便利プラグインをインストールする。
" .vimrc NeoBundle 'tpope/vim-markdown'
折り返しを有効にする
これだけでも十分なんだけど、より使いやすくするための設定を自分なりに考えてみた。まず、一行が長くなるとリストとしては見づらいので、普段は折り返さないけどmarkdownのときだけ折り返すようにしてみた。
" .vim/ftplugin/markdown.vim " 折り返しを有効にする set wrap " 80文字で折り返す set textwidth=80 " マルチバイト文字の場合も折り返しを有効にする set formatoptions+=m
Todoリストを簡単に書く
上でふれたGithubが実装したTodoリスト記法- [ ]
, - [x]
を簡単に入力するための設定も書いた。abbreviateを使うと略記を登録することができる。下の設定ではtl<space>
と入力すると- [ ]
と自動的に変換される。さらに、Todoリストのある行の上で<Leader>
を2回おすと(僕は<Leader>
を<space>
にしてる)、チェックをon/off切り替えられる。
" .vim/ftplugin/markdown.vim " todoリストを簡単に入力する abbreviate tl - [ ] " todoリストのon/offを切り替える nnoremap <buffer> <Leader><Leader> :call ToggleCheckbox()<CR> function! ToggleCheckbox() let l:line = getline('.') if l:line =~ '^\-\s\[\s\]' let l:result = substitute(l:line, '^-\s\[\s\]', '- [x]', '') call setline('.', l:result) elseif l:line =~ '^\-\s\[x\]' let l:result = substitute(l:line, '^-\s\[x\]', '- [ ]', '') call setline('.', l:result) end endfunction
スクリーンショット
普段は下のようにtmuxで画面を分割して小さいウィンドウ(右上)にTodoリストを表示しながら開発している。
vimプラグインを作ってみた
はじめてvimプラグインというものを作ってみた。コメントを折りたためるようにするだけ。zm
, zr
などで表示/非表示を切り替えられる。NeoBundleでインストールすればそのまま使える。
金曜日にmrubyの勉強会にいって、build_config.rbの大量のコメント(デフォルト値のコメントアウト)を見てウッとなって、コメントだけ非表示にできないか調べてみたら意外に簡単にできた & 手頃なプラグインがなかったので作ってみた。
コメントって読む人が初心者の場合はとても助かるけど、分かってる人にとっては邪魔なだけだから、コメントを読む人が表示/非表示を切り替えられた方がいいと思った。分かってる人には邪魔だろうなと思ってコメント書かないと初心者は困ってしまうので、読む側がコメントを見るかどうかを判断すればいいと思う。そしたら、コメント書く側は初心者のことを考えてコメントを書くようになるんじゃないかと思う。
参考
vimの便利機能
入力補完
- インサートモードで
<C-n>
または<C-p>
と打つと、補完候補が出てきます。
バッファ
- 新しいファイルを開くと、バッファという領域にその中身が読み込まれます。過去に開いたファイルをまた開くときに便利です。
- 直前に開いたファイルに戻りたい場合によく使います。
- 個人的には、前後のバッファに移動したりバッファから履歴を削除するために以下のようなマッピングを設定しています。
nnoremap <Tab> :bnext<CR> nnoremap <S-Tab> :bprevious<CR> nnoremap <Leader>d :bdelete<CR>
- これでTabやShift+Tabで前後のバッファに移動できます。
タブ
- vimにもブラウザのようなタブがあります。同時に多くのファイルを開きたいときによく使います。
- デスクトップPCであまり画面が大きくない場合、分割して複数のファイルを開くよりタブの方が出番が多いような気がします。
- 個人的には、前後のタブに移動したり新しいタブを開くために以下のようなマッピングを設定しています。
nnoremap <Leader>t :tabnew<CR> nnoremap <Leader>n :tabnext<CR> nnoremap <Leader>p :tabprev<CR>
コマンド定義
- 文字コードを変換したりインデント量を変更する操作はけっこうやるので、自分でコマンドを定義して一発で操作できるようにするとラクですね。
command! Indent2 :setlocal tabstop=2 shiftwidth=2 command! Indent4 :setlocal tabstop=4 shiftwidth=4 command! ToSjis :e ++enc=sjis<CR>
- これで普通に
:Intent2
と打てばインデント量が2になります。 - 基本は
command! <Command name> <command>
です。コマンド名は大文字から始めなくちゃいけないようです。あとはいろいろオプションがあるので、詳しくは:help command-nargs
を見てください。
abbreviate
- abbreviateは長くてめんどくさい表記に略を設定できる機能です。
abbreviate #i #include
- 例えば上のように設定すると、"#i[space]"と入力すると勝手に"#import[space]"と変換してくれます。あとはコメントブロックを入力するのによく使われるみたいです。
abbreviate #b /****************************
- abbreviateは応用としてtypoを修正するのにも便利です。"abbreviate"っていう単語がもうtypoしそうですね。あと、個人的に"receive"を"recieve"と書いてしまうことが多いので以下のように設定します。
" abbreviate <誤> <正> abbreviate abbriviate abbreviate abbreviate recieve receive
" .vim/abbreviate.vim abbreviate abbriviate abbreviate abbreviate recieve receive " .vimrc source ~/.vim/abbreviate.vim
filetype毎の設定ファイル
- 言語によってインデント量を変えたいってケースはほとんどのvimmerにあると思うんですが、そういうときに僕はfiletype毎の設定ファイルを用意しています。
- インデント量だけなら
autocmd
を使うのもアリだと思うのですが、上のabbreviateで設定したコメントブロックのように言語によって細かく設定を変えたいケースが地味にあるので、設定ファイルを用意する方法を採っています。
.vim |- ftdetect |- filetype.vim |- ftplugin |- javascript.vim |- make.vim |- ruby.vim .vimrc
- 上のようなディレクトリ構造にしておくと、各filetypeごとに設定ファイルが読み込まれるようになります。詳しくは
:help filetype-plugin
らへんを見てください。 - 例えばMakefileを書く場合、インデントはspaceではなくtabしか使えないので、expandtabを無効にしたいところです。そこで、以下のようなファイルを用意します。
" .vim/ftplugin/make.vim setlocal noexpandtab setlocal tabstop=8 setlocal shiftwidth=8
filetypeの指定
- Gemfileなど拡張子では判別できないファイルのfiletypeを指定したい場合、ftdetectが便利です。
.vim |- ftdetect |- filetype.vim .vimrc
" .vim/ftdetect/filetype.vim autocmd BufRead,BufNewFile Gemfile setfiletype ruby autocmd BufRead,BufNewFile Guardfile setfiletype ruby autocmd BufRead,BufNewFile *.rabl setfiletype ruby autocmd BufRead,BufNewFile *.jbuilder setfiletype ruby autocmd BufRead,BufNewFile *.ru setfiletype ruby
- 上のように設定ファイルを用意すると、指定したファイルを自動的にrubyをfiletypeとして開いてくれます。
以上の設定はすべて僕のdotfilesに書いてあるので参考にしてみてください。
読み返してみると、その筋の方に怒られそうな気がしてきた…(´・ω・`)
退職のお知らせ
3月末をもって、アルバイトの頃から約2年ほど勤めていた会社を退職することになりました。アルバイトの頃はRailsでサーバーサイドの開発を担当し、大学卒業後は正社員としてAndroidアプリとiOSアプリの開発を担当しました。未経験の自分にアプリ開発を任せていただけて、いろいろな面で勉強させていただきました。
4月からは、これまでとは違うフィールドで働く予定です。偶然に偶然が重なって(このブログもきっかけの一つ)、以前から興味のあった領域に携われることになり、この度転職を決意しました。これまた未経験の領域に飛び込むことになるので、これから必死に勉強する必要がありそうです。ですが、この約2年間でずいぶんと未知の領域にチャレンジしてきたので、その経験と身につけた自信でこれからも頑張っていけそうです。
これまでお世話になった皆様、有難うございました。今後とも宜しくお願い致します。