From: matz@... (Yukihiro Matsumoto) Date: 1996-01-24T12:46:22+09:00 Subject: [ruby-list:84] Ruby extension howto (sono 4) まつもと ゆきひろ@トヨタケーラムです. ruby拡張モジュール作成講座の4回目です. 一応これが最終回です.コメントは歓迎します. -- 4.例題 - dbmパッケージを作る ここまでの説明でとりあえず拡張モジュールは作れるはずです. rubyのextディレクトリにすでに含まれているdbmモジュールを例に して段階的に説明します. (1) ディレクトリを作る % mkdir ext/dbm rubyを展開したディレクトリの下,extディレクトリの中に拡張モ ジュール用のディレクトリを作ります.名前は適当に選んで構いま せん. (2) MANIFESTファイルを作る % cd ext/dbm % touch MANIFEST 拡張モジュールのディレクトリの下にはMANIFESTというファイルが 必要なので,とりあえず空のファイルを作っておきます.後でこの ファイルには必要なファイル一覧が入ることになります. MANIFESTというファイルは,makeの時にディレクトリが拡張モジュー ルを含んでいるかどうか判定するために使われれています. (3) 設計する まあ,当然なんですけど,どういう機能を実現するかどうかまず設 計する必要があります.どんなクラスをつくるか,そのクラスには どんなメソッドがあるか,クラスが提供する定数などについて設計 します.dbmクラスについてはext/dbm.docを参照してください. (4) Cコードを書く 拡張モジュール本体となるC言語のソースを書きます.rubyは拡張 モジュールをロードする時に「Init_ファイル名」という関数を自 動的に実行します.dbmモジュールの場合「Init_dbm」です. この関数の中でクラス,モジュール,メソッド,定数などの定義を 行います.dbm.cから一部引用します. -- Init_dbm() { /* DBMクラスを定義する */ cDBM = rb_define_class("DBM", cObject); /* DBMはEnumerateモジュールをインクルードする */ rb_include_module(cDBM, mEnumerable); /* DBMクラスのクラスメソッドopen(): 引数はCの配列で受ける */ rb_define_singleton_method(cDBM, "open", fdbm_s_open, -1); /* DBMクラスのメソッドclose(): 引数はなし */ rb_define_method(cDBM, "close", fdbm_close, 0); /* DBMクラスのメソッド[]: 引数は1個 */ rb_define_method(cDBM, "[]", fdbm_fetch, 1); : /* DBMデータを格納するインスタンス変数名のためのID */ id_dbm = rb_intern("dbm"); } -- DBMモジュールはdbmのデータと対応するオブジェクトになるはずで すから,Cの世界のdbmをrubyの世界に取り込む必要があります. dbm.cでは以下のマクロを使っています. -- #define MakeDBM(obj, dp) {\ DBM **_dbm;\ Make_Data_Struct(obj,id_dbm,DBM*,0,free_dbm,_dbm);\ *_dbm=dp;\ } #define GetDBM(obj, dbmp) {\ DBM **_dbm;\ Get_Data_Struct(obj, id_dbm, DBM*, _dbm);\ dbmp = *_dbm;\ if (dbmp == 0) closeddbm();\ } -- ちょっと複雑なマクロですが,要するにDBM型ポインタへのポイン タ(DBM**)をDataにカプセル化しています.DBM*を直接カプセル化 しないのはclose()した時の処理を考えてのことです. DBMクラスにはたくさんメソッドがありますが,分類すると3種類の 引数の受け方があります.ひとつは引数の数が固定のもので,例と してはdeleteメソッドがあります.deleteメソッドを実装している fdbm_delete()はこのようになっています. -- static VALUE fdbm_delete(obj, keystr) VALUE obj, keystr; { : } -- 引数の数が固定のタイプは第1引数がself,第2引数以降がメソッド の引数となります. 引数の数が不定のものはCの配列で受けるものとrubyの配列で受け るものとがあります.dbmモジュールの中で,Cの配列で受けるもの はDBMのクラスメソッドであるopen()です.これを実装している関 数fdbm_s_open()はこうなっています. -- static VALUE fdbm_s_open(argc, argv, class) int argc; VALUE *argv; VALUE class; { : if (rb_scan_args(argc, argv, "11", &file, &vmode) == 1) { mode = 0666; /* default value */ } : } -- このタイプの関数は第1引数が与えられた引数の数,第2引数が与え られた引数の入っている配列になります.selfは第3引数として与 えられます. この配列で与えられた引数を解析するための関数がopen()でも使わ れているrb_scan_args()です.第3引数に指定したフォーマットに 従い,第4変数以降に指定した変数に値を代入してくれます.この フォーマットは,第1文字目が省略できない引数の数,第2文字目が 省略できる引数の数,第3文字目が対応する相手が無いあまりの引 数があるかどうかを示す"*"です.2文字目と3文字目は省略できま す.dbm.cの例では,フォーマットは"11"ですから,引数は最低1つ で,2つまで許されるという意味になります.省略されている時の 変数の値はnil(C言語のレベルではQnil)になります. rubyの配列で引数を受け取るものはindexesがあります.実装はこ うです. -- static VALUE fdbm_indexes(obj, args) VALUE obj; struct RArray *args; { : } -- 第1引数はself,第2引数はrubyの配列です.ここではキャストを減 らすため struct RArray* で受けていますが,VALUEでも同じこと です. ** 注意事項 rubyと共有はしないがrubyのオブジェクトを格納する可能性のある Cの大域変数は以下の関数を使ってrubyインタプリタに変数の存在 を教えてあげてください.でないとGCでトラブルを起こす可能性が あります. void rb_global_variable(VALUE *var) (5) extconf.rbを用意する もしディレクトリに「extconf.rb」というファイルが存在すれば, make時に実行されます.なければ適当にMakefileが生成されます. extconf.rbはモジュールのコンパイルに必要な条件のチェックなど を行うことが目的です.extconf.rbの中では以下のruby関数を使う ことが出来ます. have_library(lib, func): ライブラリの存在チェック have_func(func): 関数の存在チェック have_header(header): ヘッダファイルの存在チェック create_makefile(target): Makefileの生成 モジュールをコンパイルする条件が揃わなず,そのモジュールはコ ンパイルしない時にはcreate_makefileを呼ばなければ良い. (6) dependを用意する もし,ディレクトリにdependというファイルが存在すれば, Makefileが依存関係をチェックしてくれます. % gcc -MM *.c > depend などで作ることが出来ます.あって損は無いでしょう. (7) MANIFESTファイルにファイル名を入れる % ls > MANIFEST % vi MANIFEST *.o, *~など不必要なファイル以外はMANIFESTに追加しておきます. make時にはMANIFESTの内容は参照しませんので,空のままでも問題 は起きないんですけど,パッケージングの時に参照することがある し,必要なファイルを区別できるんで,用意しておいた方が良いで しょう. (8) makeする rubyのディレクトリでmakeを実行するとMakefileを生成してくれま す.一度Makefileが生成されれば拡張モジュールのディレクトリの 中でmakeすることができます.extconf.rbを書き換えるなどして Makefileの再生成が必要な時はまたrubyディレクトリでmakeしてく ださい. (9) デバッグ まあ,デバッグしないと動かないでしょうね.ext/Setupにディレ クトリ名を書くと静的にリンクするのでデバッガが使えるようにな ります.その分コンパイルが遅くなりますけど. (10) できあがり 後はこっそり使うなり,広く公開するなり,売るなり,ご自由にお 使いください.rubyの作者は拡張モジュールに関して一切の権利を 主張しません.