C言語でサーバアプリやクライアントアプリを作っていると、じょじょに規模が大きくなり、アプリの設定を外出ししたいなぁ、なんて思いはじめるのはよくある事だと思います。
でも、なんとなくその設定ファイルのParser書いたり参照のインターフェイスを書くのも面倒だし引数で渡すようにするかぁ、なんて思いはじめたりもします。
でも結局引数が大量に増えだして、そのusage表示もカオスになって面倒になり(更新が止まり)、結局もう一度、「設定ファイル作るかー」なんてことになるのはよくある事だと思います。
そこで、そういう人のために,mrubyを使って簡単に設定ファイル(Rubyで書く)を外出しできるmruby-configというmrbgemを作ってみました。こういうことが簡単にできて、かつ、軽量なのがmrubyの良いところですね。
これを使うと、Rubyで書いた設定ファイルを、簡単にCソースの中で呼び出して、その値を参照することができます。
使い方
とりあえず、サーバアプリを実装中と見立てたC側のサンプルとRuby側(設定ファイル)のサンプルを見るのが一番分かりやすいでしょう。
C側は以下のように書きます。
[program lang=’c’ escaped=’true’]
#include <mruby.h> #include <mruby/variable.h> #include <mruby/hash.h> static void create_config(mrb_state *mrb, char *key, char *val) { mrb_value conf; conf = mrb_hash_new(mrb); mrb_hash_set(mrb, conf, mrb_str_new_cstr(mrb, key), mrb_str_new_cstr(mrb, val)); mrb_funcall(mrb, mrb_top_self(mrb), "new_config", 1, conf); } int main() { FILE *fp; if ((fp = fopen("./mruby.conf", "r")) == NULL) return 1; mrb_state* mrb = mrb_open(); // C側で先にconfigを作っておいてその値はRuby側で参照して動的にconfigを生成できるようにする create_config(mrb, "Version", "2"); // Ruby設定ファイル実行 mrb_load_file(mrb, fp); // 設定値の参照 int port; char *droot; mrb_value user; /* mrb_get_config_value(mrb, key, format, ...) format specifiers (like mrb_get_args): o: Object [mrb_value] S: String [mrb_value] //Not Implemented A: Array [mrb_value] //Not Implemented H: Hash [mrb_value] //Not Implemented s: String [char*,int] //Not Implemented z: String [char*] a: Array [mrb_value*,mrb_int] //Not Implemented f: Float [mrb_float] //Not Implemented i: Integer [mrb_int] */ mrb_get_config_value(mrb, "Listen", "i", &port); mrb_get_config_value(mrb, "DocumentRoot", "z", &droot); mrb_get_config_value(mrb, "User", "o", &user); // 設定の値を使って色々と処理を実装 printf("=== global configuration ===¥n"); printf("port=%d droot=%s¥n", port, droot); mrb_p(mrb, user); printf("¥n"); mrb_close(mrb); return 0; }
[/program]
このように、設定ファイルであるmruby.confを読み込んで(ここは引数で渡せるようにしておいた方が良いでしょう)そこから、設定ファイルの値をget_config_valueで参照しています。
このサンプルでは、C側で先に最低限の設定をcreate_configで作っておき、その設定に対して、Ruby設定ファイル側から設定値を上書きするような実装をしています。これによって、C側の値を参考に動的にRuby側で設定ファイルを作る事ができます。
では、その設定がかかれたRuby設定ファイルはどうなっているでしょうか。以下がその設定ファイルになります。
[program lang=’ruby’ escaped=’true’]
add_config( "Listen" => 80, "DocumentRoot" => "/var/www/html", "ExtendedStatus" => nil, "User" => "apache", "Group" => "apache", ) if get_config("Version").to_i < 2 add_config "ExtendedStatus" => "Off" else add_config "ExtendedStatus" => "On" end
[/program]
このように、基本はハッシュで渡します。このサンプルでは、C側で先に「Version」に関する設定をnew_configメソッドで作成しておき、その後にこのRuby設定ファイルを呼び出す(mruby的には実行する)事で、設定ファイル内ではC側のバージョンに関する情報を元に、それに対応した設定を生成しています。
また、特にC側で事前に設定を作っておいて、それをRuby設定ファイルで連携する必要がない場合は、以下のようにnew_configメソッドで設定を作成します。
[program lang=’ruby’ escaped=’true’]
new_config( "Listen" => 80, "DocumentRoot" => "/var/www/html", "ExtendedStatus" => "On", "User" => "apache", "Group" => "apache", )
[/program]
C側での設定値の参照はシンプルに以下の様になります。
[program lang=’c’ escaped=’true’]
#include <mruby.h> int main() { FILE *fp; if ((fp = fopen("./mruby.conf", "r")) == NULL) return 1; mrb_state* mrb = mrb_open(); mrb_load_file(mrb, fp); int port; char *droot; mrb_value user; mrb_get_config_value(mrb, "Listen", "i", &port); mrb_get_config_value(mrb, "DocumentRoot", "z", &droot); mrb_get_config_value(mrb, "User", "o", &user); // 設定の値を使って色々と処理を実装 mrb_close(mrb); return 0; }
[/program]
これでOKです。簡単ですね!
また、設定を追加・修正したい時は、
[program lang=’ruby’ escaped=’true’]
add_config "ExtendedStatus" => "On"
[/program]
と書いたり、設定そのものを削除したい場合は、
[program lang=’ruby’ escaped=’true’]
del_config "ExtendedStatus"
[/program]
と書きます。
また、設定を参照したい場合は、以下のように書きます。
[program lang=’ruby’ escaped=’true’]
get_config "ExtendedStatus"
[/program]
あるいは、設定全てを呼び出したい時は、get_configを引数無しで実行します。get_configメソッドは大抵の場合、上記のサンプルのようにC側で設定ファイルの値を参照したい時に使うと思います。そのためのmruby APIであるmrb_get_config_value()を用意しています。
[program lang=’c’ escaped=’true’]
/* mrb_get_config_value(mrb, key, format, ...) format specifiers (like mrb_get_args): o: Object [mrb_value] S: String [mrb_value] //Not Implemented A: Array [mrb_value] //Not Implemented H: Hash [mrb_value] //Not Implemented s: String [char*,int] //Not Implemented z: String [char*] a: Array [mrb_value*,mrb_int] //Not Implemented f: Float [mrb_float] //Not Implemented i: Integer [mrb_int] */ int port; char *droot; mrb_value user; mrb_get_config_value(mrb, "Listen", "i", &port); mrb_get_config_value(mrb, "DocumentRoot", "z", &droot); mrb_get_config_value(mrb, "User", "o", &user);
[/program]
mruby-configで定義しているメソッドは全てC側からもRuby側からも値を共有できるように実装しているので、好きなように両者からメソッドを実行すると良いでしょう。
続きも書きました。
mruby-configにtag付のconfigを書けるようにした
最後に
今回はCで書いたサーバアプリやクライアントアプリでパラメータを柔軟に扱いたい場合の一つの手法として、mrubyを用いて簡単に設定値を外部のファイル(Rubyスクリプト)と連携するためのmruby-configを使った手法を紹介しました。これで、単純な設定であれば、ほぼ何も考える事なくCで書かれたアプリケーションの設定ファイルを外出しすることができるでしょう。
個人的にも、設定ファイルを作るのって面倒に思っていたので、今後はmruby-configを使って楽に外出しした設定ファイルからCアプリ内に値をひっぱってくるようにしたいと思います。