LoginSignup
14
4

More than 5 years have passed since last update.

mruby 逆引きリファレンス (落丁版) (💣💣💣)

Last updated at Posted at 2018-04-01

個人的な、ちょっとアレをするにはどうするんだったっけ? を編纂したものです。主に C 言語からの利用視点となっています。

dearblue の雑記帳の扱いとなっているため、理想となる逆引き辞典のような記述にはなっておりません。

中には mruby 開発者たちが見たら「その方法は間違いだし危険だからやめて」と悲鳴を上げるようなモノが含まれている可能性が1㍉くらいあります。

正確性が怪しかったり推奨されなかったりするものだと胸を張って主張する項目には、💣をつけておきます。複数あったらより危険だということです。その上で当文書の題名を確認してもらうと……ニヤリ。

mruby-1.4 が対象と思いきや、mruby-1.3 や 1.2 向けの記述が混じっています。

当文書を参考にしたことで星間戦争を引き起こす原因を作ったとしても、責任は負いません。

保険適用外。公共の場に持ち出すと真っ先に狙撃されます。もう何も怖くない

mruby の作成

REF:

mruby 最短ビルド方法

前提: すでに GCC 系のコンパイラ、ruby および git がインストールされていること。

$ git clone https://github.com/mruby/mruby.git mruby
$ cd mruby
$ ruby minirake

で、bin/mruby が動作できる形で作成される。

ビルド設定を色々いじりたい

build_config.rb を編集する。

異なるビルド設定ファイルを指定したい

環境変数 MRUBY_CONFIG で任意のファイル名・パスに変更することが出来る。

何も指定しない場合は、mruby のソースコードのトップディレクトリに置かれた build_config.rb が使われる。

$ export MRUBY_CONFIG=custom_build.rb
$ rake -f ~/mruby-1.3/Rakefile

詳細は https://github.com/mruby/mruby/blob/1.4.0/doc/guides/compile.mdhttps://github.com/mruby/mruby/blob/1.4.0/doc/guides/mrbconf.md を確認。

Fixnum の範囲を変更したい

build_config.rbconf.cc.definesMRB_INT16MRB_INT32 (mruby-1.4 で追加)、MRB_INT64 を追加する。

(mruby-1.4 以前) 何も指定しない場合は、MRB_INT32 相当の設定となる (MRB_INT32 自体は存在しない)。

(mruby-1.4 から) 何も指定しない場合は、sizeof(void *) を含めることのできる最小の整数型が選択される。

MRuby::Build.new do |conf|
  conf.cc.defines << "MRB_INT64"
end
  • NOTE: 整数値の最小値・最大値は C では MRB_INT_MIN / MRB_INT_MAX で確認する。
  • NOTE: mrb_str_buf_new()mrb_str_resize() などで指定できる capacity 値は、(sizeof(size_t) - 1)(MRB_INT_MAX - 1) の小さい方が上限となる。

mrb_value の表現方法を変更したい

build_config.rbconf.cc.definesMRB_NAN_BOXING または MRB_WORD_BOXING を追加する。

両方とも指定しない場合は、MRB_NO_BOXING 相当となる ( MRB_NO_BOXING 自体は存在しない)。

MRuby::Build.new do |conf|
  conf.cc.defines << "MRB_WORD_BOXING"
end

(mruby-1.4 以前) ruby の VALUE と同等にしたい場合は、MRB_WORD_BOXING と、必要であれば MRB_INT64 も定義する。
(mruby-1.4 以降) ruby の VALUE と同等にしたい場合は、MRB_WORD_BOXING を定義する。

MRuby::Build.new do |conf|
  conf.cc.defines << "MRB_WORD_BOXING"
  conf.cc.defines << "MRB_INT64" if [nil].pack("P").bytesize > 4
end

REF:

テストしたい

conf.enable_test を記述する。

MRuby::Build.new do |conf|
  conf.enable_test
end

test/*.rb を作っておくと、host/bin/mrbtest でテスト結果を確認できるようになる。

C++ が混ざっている場合

mruby のビルドシステムは C++ のコードが混ざっていると判断すると、C++ の例外機構 (try-cache-throw) のための構成を自動で有効にする。

利用者が手動で有効にしたい場合、build_config.rbconf.enable_cxx_exception を記述する。

MRuby::Build.new do |conf|
  conf.enable_cxx_exception
end

もっと強力に、C のコードも問答無用で C++ としてコンパイルしたい場合のために conf.enable_cxx_abi も用意されている。

もし何らかの事情があって conf.enable_cxx_exception されると困る場合は conf.disable_cxx_exception を記述する。
これはすべての conf.enable_cxx_exceptionconf.gem に先立って記述しなければならない。

ライブラリの追加方法 (MRuby::Build#gem)

https://github.com/mruby/mruby/blob/1.4.0/doc/guides/mrbgems.md も確認するべし。

mruby の拡張ライブラリは mrbgems として管理されている。mruby に標準で付属するものだけではなく、第三者が公開しているものを利用することも可能。

MRuby::Build.new do |conf|
  # mruby に標準で付属しているものを指定
  gem core: "mruby-print"

  # https://github.com/mruby/mgem-list に登録されたものを指定
  gem mgem: "mruby-print"

  # github にあるものを指定
  gem github: "mruby/mruby-print"

  # bitbucket にあるものを指定
  gem bitbucket: "mruby/mruby-print"

  # git で取得可能なものを指定
  gem git: "https://github.com/mruby/mruby-print.git"

  # 自分のコンピュータのみに存在する、オレオレ mgem を指定
  gem "/home/mruby/mrbgems/mruby-print"
end

mrbgem.rake

mruby gem パッケージとして必要となる情報が記述されたファイル。中身は ruby スクリプト。

詳細は https://github.com/mruby/mruby/blob/1.4.0/doc/guides/mrbgems.md にまとまってる。

オブジェクト全般

C の型

  • mruby コンテキスト :: mrb_state (ポインタとして使う)
  • 整数型 :: mrb_int
  • 浮動小数点数型 :: mrb_float
  • 真偽値型 :: mrb_bool
  • オブジェクト型 :: mrb_value

データタイプ一覧

/* include/mruby/value.h */
    96: enum mrb_vtype {
    97:   MRB_TT_FALSE = 0,   /*   0 */

    99:   MRB_TT_TRUE,        /*   2 */
   100:   MRB_TT_FIXNUM,      /*   3 */
   101:   MRB_TT_SYMBOL,      /*   4 */

   103:   MRB_TT_FLOAT,       /*   6 */
   104:   MRB_TT_CPTR,        /*   7 */
   105:   MRB_TT_OBJECT,      /*   8 */
   106:   MRB_TT_CLASS,       /*   9 */
   107:   MRB_TT_MODULE,      /*  10 */
   108:   MRB_TT_ICLASS,      /*  11 */
   109:   MRB_TT_SCLASS,      /*  12 */
   110:   MRB_TT_PROC,        /*  13 */
   111:   MRB_TT_ARRAY,       /*  14 */
   112:   MRB_TT_HASH,        /*  15 */
   113:   MRB_TT_STRING,      /*  16 */
   114:   MRB_TT_RANGE,       /*  17 */
   115:   MRB_TT_EXCEPTION,   /*  18 */
   116:   MRB_TT_FILE,        /*  19 */
   117:   MRB_TT_ENV,         /*  20 */
   118:   MRB_TT_DATA,        /*  21 */
   119:   MRB_TT_FIBER,       /*  22 */
   120:   MRB_TT_ISTRUCT,     /*  23 */

/* 以下は利用者が使用する場合は特に注意が必要なデータタイプ (たぶん) */
    98:   MRB_TT_FREE,        /*   1 */
   102:   MRB_TT_UNDEF,       /*   5 */
   121:   MRB_TT_BREAK,       /*  24 */
   122:   MRB_TT_MAXDEFINE    /*  25 */

   123: };

データタイプの確認

/* include/mruby/object.h */
mrb_bool mrb_immediate_p(mrb_value obj);

/* include/mruby/value.h */
mrb_bool mrb_fixnum_p(mrb_value obj);
mrb_bool mrb_undef_p(mrb_value obj);
mrb_bool mrb_nil_p(mrb_value obj);
mrb_bool mrb_bool(mrb_value obj);
mrb_bool mrb_float_p(mrb_value obj);
mrb_bool mrb_symbol_p(mrb_value obj);
mrb_bool mrb_array_p(mrb_value obj);
mrb_bool mrb_string_p(mrb_value obj);
mrb_bool mrb_hash_p(mrb_value obj);
mrb_bool mrb_cptr_p(mrb_value obj);
mrb_bool mrb_exception_p(mrb_value obj);

オブジェクトタイプの特徴 (クラスではないことに注意)

※mruby-1.4 を対象としている。

タイプ名 クラス C の型 即値か 1 ファイナライザの設置 インスタンス変数の保持 備考
MRB_TT_FALSE NilClass / FalseClass mrb_bool はい 不可 不可
MRB_TT_TRUE TrueClass mrb_bool はい 不可 不可
MRB_TT_FIXNUM Fixnum mrb_int はい 不可 不可
MRB_TT_SYMBOL Symbol mrb_sym はい 不可 不可
MRB_TT_FLOAT Float mrb_float はい 不可 不可
MRB_TT_CPTR Object 任意 はい 不可 不可 任意のポインタをカプセル化する
MRB_TT_OBJECT 任意 struct RObject いいえ 不可
MRB_TT_CLASS 任意 struct RClass いいえ 不可
MRB_TT_MODULE 任意 struct RClass いいえ 不可
MRB_TT_ICLASS 任意 struct RClass いいえ 不可 不可
MRB_TT_SCLASS 任意 struct RClass いいえ 不可
MRB_TT_PROC 任意 struct RProc いいえ 不可 不可
MRB_TT_ARRAY 任意 struct RArray いいえ 不可 不可
MRB_TT_HASH 任意 struct RHash いいえ 不可
MRB_TT_STRING 任意 struct RString いいえ 不可 不可
MRB_TT_RANGE 任意 struct RRange いいえ 不可 不可
MRB_TT_EXCEPTION 任意 struct RException いいえ 不可
MRB_TT_FILE 任意 ? いいえ 不可 不可
MRB_TT_ENV 任意 struct REnv いいえ 不可 不可
MRB_TT_DATA 任意 struct RData いいえ 任意のポインタをカプセル化する
MRB_TT_FIBER 任意 struct RFiber いいえ 不可 不可
MRB_TT_ISTRUCT 任意 struct RIstruct いいえ 不可 不可 3ワード領域を自由に利用可能

/* include/mruby/value.h */
MRB_INLINE mrb_value mrb_float_value(struct mrb_state *mrb, mrb_float f);
mrb_value mrb_cptr_value(struct mrb_state *mrb, void *p);
mrb_value mrb_obj_value(void *p);
MRB_INLINE mrb_value mrb_nil_value(void);
MRB_INLINE mrb_value mrb_false_value(void);
MRB_INLINE mrb_value mrb_true_value(void);
mrb_value mrb_bool_value(mrb_bool boolean);
mrb_value mrb_undef_value(void);

インスタンス変数

/* include/mruby/variable.h */
void mrb_iv_set(mrb_state *mrb, mrb_value obj, mrb_sym sym, mrb_value v);
mrb_value mrb_iv_get(mrb_state *mrb, mrb_value obj, mrb_sym sym);
mrb_bool mrb_iv_defined(mrb_state *mrb, mrb_value obj, mrb_sym sym);
mrb_value mrb_iv_remove(mrb_state *mrb, mrb_value obj, mrb_sym sym);
void mrb_iv_copy(mrb_state *mrb, mrb_value dst, mrb_value src);

インスタンス変数を利用できるオブジェクトタイプ (クラスではなく mrb_type のこと) は、MRB_TT_OBJECT MRB_TT_CLASS MRB_TT_MODULE MRB_TT_SCLASS MRB_TT_HASH MRB_TT_DATA MRB_TT_EXCEPTION に限定される。

REF: https://github.com/mruby/mruby/blob/1.4.0/src/variable.c#L222

オブジェクトの生成

通常は mrb_obj_new() を使う。

初期化 (.initialize) を伴わない、オブジェクトの確保のみを行いたい場合は mrb_obj_alloc() がある。

/* include/mruby.h */
MRB_API mrb_value mrb_obj_new(mrb_state *mrb, struct RClass *c, mrb_int argc, const mrb_value *argv);
MRB_API struct RBasic *mrb_obj_alloc(mrb_state*, enum mrb_vtype, struct RClass*);

オブジェクトの入れ物が同じかどうかを確認する

mrb_obj_eq() を使う。mrb_obj_equal() は mruby-1.3 の時点では同じ同じだが、『temporary definition』となっているため、将来的に異なる可能性がある。

/* include/mruby.h */
MRB_API mrb_bool mrb_obj_eq(mrb_state*, mrb_value, mrb_value);
MRB_API mrb_bool mrb_obj_equal(mrb_state*, mrb_value, mrb_value);

オブジェクの内容物まで比較して等価かどうかを確認する

mrb_equal() を使うと、.== メソッドによる比較が行われる。

mrb_eql() を使うと、.eql? メソッドによる比較が行われる。

/* include/mruby.h */
MRB_API mrb_bool mrb_equal(mrb_state *mrb, mrb_value obj1, mrb_value obj2);
MRB_API mrb_bool mrb_eql(mrb_state *mrb, mrb_value obj1, mrb_value obj2);

mrb_state 毎のグローバル変数として使う方法 (💣)

mruby の実行環境は mrb_state 毎に分離されるため、一つのプロセスの中で複数の mrb_state を持つ場合にグローバル変数を用いることが事実上出来ないことが稀によくある。

そんな時は mrb_gv_set() で設定し、mrb_gv_get() で取得、mrb_gv_remove() で削除できる。この時のグローバル変数名を $ 以外で始めれば mruby 空間からは隠蔽されるので都合がいい (💣:検証だけは済ませた)。

mrb_XXX_gem_init()mrb_XXX_gem_final() で初期化と後始末を行える。この際には mrb_cptr_value()mrb_cptr() が役に立ちそうだ。

/* mruby/variable.h */
mrb_value mrb_gv_get(mrb_state * mrb, mrb_sym sym);
void mrb_gv_set(mrb_state * mrb, mrb_sym sym, mrb_value val);
void mrb_gv_remove(mrb_state * mrb, mrb_sym sym);

ヒープメモリの管理

/* mruby/include/mruby.h */
MRB_API void *mrb_malloc(mrb_state *, size_t);         /* メモリが確保できない場合は RuntimeError 例外が発生する */
MRB_API void *mrb_calloc(mrb_state *, size_t, size_t); /* 上に同じ */
MRB_API void *mrb_realloc(mrb_state *, void *, size_t); /* 上に同じ */
MRB_API void *mrb_malloc_simple(mrb_state *, size_t);  /* メモリが確保できない場合は NULL を返す */
MRB_API void *mrb_realloc_simple(mrb_state *, void *, size_t); /* 上に同じ */
MRB_API void mrb_free(mrb_state *, void *);

NOTE: mrb_malloc_simple() / mrb_realloc_simple() 関数の使用は推奨されない。

GC

GC アリーナ

参考 (と言うか必読): mrubyのmrb_gc_arena_save()/mrb_gc_arena_restore()の使い方 - Matzにっき(2013-07-31)

/* include/mruby.h */
MRB_API int mrb_gc_arena_save(mrb_state*);
MRB_API void mrb_gc_arena_restore(mrb_state*,int);
MRB_API void mrb_gc_protect(mrb_state *mrb, mrb_value obj);

特に繰り返し処理中にオブジェクトをその度に生成させているような場合は必須。

int ai = mrb_gc_arena_save(mrb); /* 変数名の ai は arena index を意味する */
mrb_value str;

while (めちゃくちゃループ) {
    str = mrb_str_new_cstr(mrb, "ほげほげ");

    /* str を面白可笑しくいじる */

    mrb_gc_arena_restore(mrb, ai); /* アリーナ領域を ai に復帰 */
}

/*
 * 最後の str だけはどうしても必要だった!
 * mrb_gc_arena_restore() 直後なら GC が実行されないので、チャンスは今だけ!
 */
mrb_gc_protect(mrb, str);

/* str を面白可笑しくいじる */

/* やっぱ str はいらねーや (場合によっては省略できる) */
mrb_gc_arena_restore(mrb, ai);

メソッド

メソッドを呼ぶ

/* mruby/include/mruby.h */
MRB_API mrb_value mrb_funcall(mrb_state*, mrb_value, const char*, mrb_int,...);
MRB_API mrb_value mrb_funcall_argv(mrb_state*, mrb_value, mrb_sym, mrb_int, const mrb_value*);
MRB_API mrb_value mrb_funcall_with_block(mrb_state*, mrb_value, mrb_sym, mrb_int, const mrb_value*, mrb_value);

呼ばれた時のメソッド名を知りたい

mrb_get_mid() で取得できる。

mrb_sym mid = mrb_get_mid(mrb);

mruby-1.2 以前には存在しないので、mrb_state から直接釣り上げる。

mrb_sym mid = mrb->c->ci->mid;

引数を取得したい

登録された関数が mruby 空間からメソッドとして呼び出された場合、引数の取得は mrb_get_args() を使う。
この時渡す format には文字列を与えることとなり、その詳細は https://mruby.org/docs/api/headers/mruby.h.html#mrb_args_format-typedef あるいは https://github.com/mruby/mruby/blob/1.4.0/src/class.c#L571-L600 を参照されたい。

他には引数長を取得するだけの mrb_get_argc() がある。

引数の配列としてのポインタを取得する mrb_get_argv() もあるが、NULL を返す場合があるため、mrb_get_args(mrb, "*", &argv, &argc) 2 を使ったほうが好ましい。

/* 1.4.0:include/mruby.h */
MRB_API mrb_int mrb_get_args(mrb_state *mrb, mrb_args_format format, ...);
MRB_API mrb_int mrb_get_argc(mrb_state *mrb);
MRB_API mrb_value* mrb_get_argv(mrb_state *mrb);

Method

/* include/mruby.h */
MRB_API void mrb_define_method(mrb_state *mrb, struct RClass *cla, const char *name, mrb_func_t func, mrb_aspec aspec);
MRB_API void mrb_define_class_method(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec);
MRB_API void mrb_define_singleton_method(mrb_state*, struct RObject*, const char*, mrb_func_t, mrb_aspec);
MRB_API void mrb_define_alias(mrb_state *mrb, struct RClass *klass, const char *name1, const char *name2);
MRB_API void mrb_undef_method(mrb_state*, struct RClass*, const char*);
MRB_API void mrb_undef_class_method(mrb_state*, struct RClass*, const char*);

/* include/mruby/class.h */
MRB_API void mrb_define_method_raw(mrb_state*, struct RClass*, mrb_sym, mrb_method_t);
MRB_API void mrb_define_method_id(mrb_state *mrb, struct RClass *c, mrb_sym mid, mrb_func_t func, mrb_aspec aspec);
MRB_API void mrb_alias_method(mrb_state *mrb, struct RClass *c, mrb_sym a, mrb_sym b);
MRB_API mrb_method_t mrb_method_search(mrb_state*, struct RClass*, mrb_sym);
MRB_API mrb_method_t mrb_method_search_vm(mrb_state*, struct RClass**, mrb_sym);

/* include/mruby/proc.h */
void MRB_METHOD_FROM_FUNC(mrb_method_t &method, mrb_func_t func); /* 実体はマクロ */
void MRB_METHOD_FROM_PROC(mrb_method_t &method, struct RProc *proc); /* 実体はマクロ */

NOTE: mrb_define_method() 系の aspec 引数は飾り (※mruby-1.4 現在)。引数の確認は func が呼ばれて mrb_get_args() を通して行う (mrb_get_args() に関しては引数を取得したいを参照されたい)。

Proc

/* include/mruby/proc.h */
struct RProc *mrb_proc_ptr(mrb_value v); /* 実体はマクロ */
struct RProc *mrb_proc_new(mrb_state *mrb, mrb_irep *irep);
struct RProc *mrb_closure_new(mrb_state *mrb, mrb_irep *irep);
void mrb_proc_copy(struct RProc *a, struct RProc *b);
MRB_API struct RProc *mrb_proc_new_cfunc(mrb_state *mrb, mrb_func_t func);
MRB_API struct RProc *mrb_closure_new_cfunc(mrb_state *mrb, mrb_func_t func, int nlocals);
MRB_API struct RProc *mrb_proc_new_cfunc_with_env(mrb_state *mrb, mrb_func_t func, mrb_int nenv, const mrb_value *env);
MRB_API mrb_value mrb_proc_cfunc_env_get(mrb_state *mrb, mrb_int idx);
mrb_value mrb_cfunc_env_get(mrb_state *mrb, mrb_int idx); /* 実体はマクロ; mrb_proc_cfunc_env_get の別名 */
mrb_bool MRB_PROC_CFUNC_P(struct RProc *p); /* 実体はマクロ */
mrb_func_t MRB_PROC_CFUNC(struct RProc *p); /* 実体はマクロ */

mrb_proc_new_cfunc_with_env() で Proc に関連付けられた値は、mrb_define_method_raw() によって登録された Proc がメソッド呼び出しされ、その中で mrb_cfunc_env_get() することで受け取ることが出来る。

mrb_closure_new_cfunc()nlocals の分だけ領域を確保するだけで mrb_proc_new_cfunc_with_env() と同じ。env へ値を代入させる手順はちょっと込み入ってるし、利用場面はない?

Yield するには?

mrb_yield() または mrb_yield_argv() を使う。ただし @yukihiro_matz さんは C コードからの利用を避けたい としている (勝手な想像をすると、ファイバーとの兼ね合い?)

整数値 (Integer / Fixnum)

データタイプは MRB_TT_FIXNUM

データタイプの確認は mrb_bool mrb_fixnum_p(mrb_value obj) で行える。

最小値・最大値は MRB_INT_MINMRB_INT_MAX で確認する。

mruby は ruby と異なり、標準では多倍長整数が扱えない。

第三者による mruby-bignum が利用できるかもしれない。

mruby の値から C の整数値に変換する

mrb_int() を使う。オブジェクトが Fixnum であることを保証している場合は mrb_fixnum() が使える。

/* include/mruby.h */
mrb_int mrb_int(mrb_state *, mrb_value); /* 実体はマクロ */

/* include/mruby/boxing_XXX.h */
mrb_int mrb_fixnum(mrb_value); /* 実体はマクロ */

C の整数値から mruby の値に変換する

mrb_fixnum_value() を使う。

/* include/mruby/value.h */
MRB_INLINE mrb_value mrb_fixnum_value(mrb_int i);

実数値 (Float)

実体は double だが、build_config.rbconf.cc.definesMRB_USE_FLOAT を追加することで float にすることが出来る。

C の型は mrb_float

/* include/mruby.h (実体は include/mruby/boxing_XXX.h) */
mrb_float mrb_float(mrb_value obj); /* 実体はマクロ */

/* include/mruby/value.h */
MRB_INLINE mrb_value mrb_float_value(struct mrb_state *mrb, mrb_float f);
mrb_bool mrb_float_p(mrb_value obj); /* 実体はマクロ */

シンボル (Symbol)

C の型は mrb_sym。データタイプは MRB_TT_SYMBOL

/* 1.4.0:include/mruby.h */
MRB_API const char *mrb_sym2name(mrb_state*,mrb_sym);
MRB_API const char *mrb_sym2name_len(mrb_state*,mrb_sym,mrb_int*);
MRB_API mrb_value mrb_sym2str(mrb_state*,mrb_sym);

/* 1.4.0:include/mruby/value.h */
mrb_bool mrb_symbol_p(mrb_value); /* 実体はマクロ */
static inline mrb_value mrb_symbol_value(mrb_sym i);

/* 1.4.0:include/mruby.h (実体は include/mruby/boxing_XXX.h) */
mrb_sym mrb_symbol(mrb_value obj); /* 実体はマクロ */

C の文字列をシンボルに変換したい

/* 1.4.0:include/mruby.h */
MRB_API mrb_sym mrb_intern_cstr(mrb_state*,const char*);
MRB_API mrb_sym mrb_intern(mrb_state*,const char*,size_t);
MRB_API mrb_sym mrb_intern_static(mrb_state*,const char*,size_t);
MRB_API mrb_sym mrb_intern_str(mrb_state*,mrb_value);
mrb_sym mrb_intern_lit(mrb_state*,const char*); /* 実体はマクロ */
MRB_API mrb_sym mrb_obj_to_sym(mrb_state *mrb, mrb_value name);

基本的に mrb_intern_cstr() を使う。
文字列オブジェクト (MRB_TT_STRING のこと) であることが保証されていれば mrb_intern_str() が使える。

リテラル文字列であれば mrb_intern_lit() を用いることで、ヒープメモリの確保とメモリ間コピーがされなくなるため美味しい思いが出来る。
同様の理由で static const な文字列であることが明らかであれば mrb_intern_static() を用いることができる。

ただしリテラル文字列でない文字列を mrb_intern_lit()mrb_intern_static() に与えると、大泣きする羽目になるので注意。

ポインタの先が何なのか判断できない場合は、安全のために mrb_intern_cstr() を使うべき。

mruby の真偽値

/* include/mruby/value.h */
mrb_bool mrb_bool(mrb_value obj); /* 実体はマクロ */
mrb_bool mrb_test(mrb_value obj); /* 実体はマクロ */

クラス・モジュール

/* include/mruby/value.h */
static inline mrb_value mrb_obj_value(struct RClass *p);

/* include/mruby/class.h */
struct RClass *mrb_class_ptr(mrb_value); /* 実体はマクロ */

クラスの定義

MRB_API struct RClass *mrb_define_class(mrb_state *mrb, const char *name, struct RClass *super);
MRB_API struct RClass * mrb_define_class_under(mrb_state *mrb, struct RClass *outer, const char *name, struct RClass *super);
struct RClass *superklass = ...;
const char *klassname = ...;

struct RClass *klass = mrb_define_class(mrb, name, superklass);

モジュールやクラスの内部に定義する場合は次の通り。

struct RClass *superklass = ...;
const char *klassname = ...;
struct RClass *outerklass = ...;

struct RClass *klass = mrb_define_class_under(mrb, outerklass, name, superklass);

クラス名を取得する

オブジェクトがクラス・モジュールであれば mrb_class_name() を使う。

インスタンスのクラス名を得たい場合は mrb_obj_classname() を使う。

/* include/mruby.h */
MRB_API const char *mrb_class_name(mrb_state *mrb, struct RClass* klass);
MRB_API const char *mrb_obj_classname(mrb_state *mrb, mrb_value obj);

C の文字列からクラス・モジュールを取得する

MRB_API struct RClass *mrb_class_get(mrb_state *mrb, const char *name);
MRB_API struct RClass *mrb_class_get_under(mrb_state *mrb, struct RClass *outer, const char *name);

name 引数には :: を含めることが出来ないため、一つ一つを辿っていく必要がある。

#define DIG_CLASS(mrb, ...)                                             \
    adig_class(mrb,                                                     \
        sizeof((const char *[]){ __VA_ARGS__ }) / sizeof(const char *), \
        (const char *[]){ __VA_ARGS__ })                                \

static mrb_value
adig_class(mrb_state *mrb, size_t n, const char *names[])
{
    struct RClass *klass = mrb->object_class;
    for (size_t i = 0; i < n; i ++) {
        klass = mrb_class_get_under(mrb, klass, names[i]);
    }
    return klass;
}

スーパークラス (継承元クラス) の取得

struct RClass *RCLASS_SUPER(mrb_value klass);

ただし、klass オブジェクトはクラスオブジェクトでなければならない。

あるいは:

struct RClass *klass = ...;
struct RClass *super = klass->super;

クラス・モジュールの定義の確認

mrb_bool mrb_class_defined(mrb_state *mrb, const char *name);
mrb_bool mrb_class_defined_under(mrb_state *mrb, struct RClass *outer, const char *name);

クラスのインスタンスかどうかの確認

/* include/mruby.h */
mrb_bool mrb_obj_is_kind_of(mrb_state *mrb, mrb_value obj, struct RClass *c);

文字列

データタイプは MRB__TT_STRING。C における実体は struct RString で、ポインタとして扱う。

/* 1.4.0:include/mruby/value.h */
mrb_bool mrb_string_p(mrb_value); /* 実体はマクロ */

struct RString *mrb_str_ptr(mrb_value); /* 実体はマクロ */
struct RString *RSTRING(mrb_value);     /* 実体はマクロ */

文字列を生成する方法・文字列に変換する方法

/* include/mruby.h */
MRB_API mrb_value mrb_str_new(mrb_state *mrb, const char *p, size_t len);
MRB_API mrb_value mrb_str_new_cstr(mrb_state*, const char*);
MRB_API mrb_value mrb_str_new_static(mrb_state *mrb, const char *p, size_t len);
mrb_value mrb_str_new_lit(mrb_state*, const char*); /* 実体はマクロ */

MRB_API mrb_value mrb_any_to_s(mrb_state *mrb, mrb_value obj);

/* include/mruby/string.h */
MRB_API mrb_value mrb_str_new_capa(mrb_state *mrb, size_t capa);
MRB_API mrb_value mrb_str_buf_new(mrb_state *mrb, size_t capa);
MRB_API mrb_value mrb_str_dup(mrb_state *mrb, mrb_value str);
MRB_API mrb_value mrb_ptr_to_str(mrb_state *, void*);
MRB_API mrb_value mrb_obj_as_string(mrb_state *mrb, mrb_value obj);
MRB_API mrb_value mrb_str_to_str(mrb_state *mrb, mrb_value str);

/* include/mruby/numeric.h */
MRB_API mrb_value mrb_fixnum_to_str(mrb_state *mrb, mrb_value x, mrb_int base);
MRB_API mrb_value mrb_float_to_str(mrb_state *mrb, mrb_value x, const char *fmt);

判断早見表 (あくまで目安)

  1. 文字列オブジェクトに変換するデータがまだない
    1. 一定の領域を予約した空文字列が欲しい
      1. 予約した領域を拡大させる予定がある → mrb_str_buf_new() を使う
      2. 予約領域はそのまま・予約領域を拡大させるかはまだわからない → mrb_str_new_capa() を使う
    2. 空文字列が欲しい → mrb_str_new(mrb, NULL, 0) を使う
  2. mruby のオブジェクトを変換したい
    1. クラスとオブジェクト ID を組み合わせた表記にしたい → mrb_any_to_s() を使う
    2. オブジェクト ID を整形して文字列にしたい → mrb_ptr_to_str() を使う
    3. mruby の文字列オブジェクトをコピーしたい → mrb_str_dup() を使う
    4. 整数オブジェクトや浮動小数点数オブジェクトを変換したい → mrb_fixnum_to_str() または mrb_float_to_str() 辺りを使うといいかもしれない
    5. mruby の任意のオブジェクトを文字列にしたい → mrb_str_to_str() を使う
  3. C の文字列を変換したい
    1. リテラル文字列である → mrb_str_new_lit() を使う
    2. static const な文字列である → mrb_str_new_static() を使う
    3. どちらでもない・判断できない → mrb_str_new_cstr() を使う
  4. バイナリデータ、または文字列か判断できないデータを文字列オブジェクトにしたい → mrb_str_new() を使う
  5. それ以外 → 変換処理を自作するか /dev/null に突っ込む

NOTE:

  • mrb_str_new_lit()mrb_str_new_static() の特徴と注意は? mrb_intern_lit()mrb_intern_static() と同様、メモリの節約が出来ると同時にメモリ間コピーがないため CPU 資源・電力の節約にもつながる。 またリテラル文字列・static const 文字列両方に当てはまらない場合は使うと、ここぞという時に泣く羽目になるのも同じ。
  • mrb_obj_as_string()mrb_str_to_str() と何が違う? mrb_obj_as_string() はうまく文字列オブジェクトへ変換できない場合に最終手段として mrb_any_to_s() し、必ず文字列オブジェクトに変換する (が、望まない形となるかもしれない)。 その点 mrb_str_to_str() は最終手段がなくてコケる (例外を起こす) ようになっている。

C の文字列を取り出す

mruby の String から C 言語の文字列を取り出す正しい方法 - Qiita に詳しく書かれている。

/* 1.4.0:include/mruby/string.h */
char *RSTR_PTR(struct RString *); /* 実体はマクロ */
char *RSTRING_PTR(mrb_value);     /* 実体はマクロ */
char *RSTRING_END(mrb_value);     /* 実体はマクロ */
MRB_API const char *mrb_string_value_cstr(mrb_state *mrb, mrb_value *ptr);
MRB_API const char *mrb_string_value_ptr(mrb_state *mrb, mrb_value str);

C のポインタ・構造体

/* mruby/include/mruby/data.h */
typedef struct mrb_data_type {
  /** data type name */
  const char *struct_name;

  /** data type release function pointer */
  void (*dfree)(mrb_state *mrb, void*);
} mrb_data_type;

MRB_API struct RData *mrb_data_object_alloc(mrb_state *mrb, struct RClass *klass, void *datap, const mrb_data_type *type);
MRB_API void mrb_data_check_type(mrb_state *mrb, mrb_value, const mrb_data_type*);
MRB_API void *mrb_data_get_ptr(mrb_state *mrb, mrb_value, const mrb_data_type*);
MRB_API void *mrb_data_check_get_ptr(mrb_state *mrb, mrb_value, const mrb_data_type*);

#define Data_Wrap_Struct(mrb,klass,type,ptr)
#define Data_Make_Struct(mrb,klass,strct,type,sval,data)
#define Data_Get_Struct(mrb,obj,type,sval)
#define DATA_GET_PTR(mrb,obj,dtype,type)
#define DATA_PTR(d)
#define DATA_TYPE(d)
struct RData *Data_Wrap_Struct(mrb_state *mrb, struct RClass *klass, const mrb_data_type *type, void *ptr);
void Data_Make_Struct(mrb_state *mrb, struct RClass *klass, TYPE_NAME, const mrb_data_type *type, USER_TYPE *&sval, struct RData *&data);
void Data_Get_Struct(mrb_state *mrb, mrb_value obj, const mrb_data_type *type, USER_TYPE *&sval);
USER_TYPE *DATA_GET_PTR(mrb_state *mrb, mrb_value obj, const mrb_data_type *dtype, TYPE_NAME);

(注意) 利用者定義の構造体には mrb_value を保持させてはならない

正確に言うと保持させること自体は可能だが、それだけだとそのオブジェクトは GC のマーク対象とはならないため、mruby の GC フェーズで回収対象となる。

紐付けるためには、mrb_iv_set() / mrb_iv_get() を使ってやり取りする。

この事は issues#2605 で説明があり、世代別/インクリメンタル GC との相性を理由としている。

構造体にもオブジェクトを保持させる場合は mrb_iv_set() で紐付けしたオブジェクトと整合性を保つ (構造体とインスタンス変数の両方で同じオブジェクトとなる) ようにする。

一時的にポインタをラッピングしたい

mrb_data_object_alloc() を用いるまでもないが、一時的にポインタを mrb_value として扱う必要がある場合は mrb_cptr / mrb_cptr_value を利用する。

mrb_rescue()mrb_ensure() などで活躍。

mruby の GC が free したり、mrb_data_type で確認したりするための仕組みがないため、簡易な mrb_data_object_alloc() として代わりに使うべきではない。

GC 対象外。アリーナを消費しない。インスタンス変数の保持不可。クラスを持てない (ゆえに特異メソッドも持てない)。

void *anyptr = ...;
mrb_value ptrobj = mrb_cptr_value(mrb, anyptr);

...そびえる関数の壁...

void *gotptr = mrb_cptr(ptrobj);

NOTE: mruby 空間に持ち出すことは可能だが、一度でも自分の手を離れた MRB_TT_CPTR オブジェクトはそれが本当に自分が流したオブジェクトなのか確認できないし、スコープ外・解放済みとなっている場合があるため触るべきではない。

Inline Structures (mruby-1.3+)

3ワード分 (sizeof(void *) * 3) だけ確保されたメモリ領域を、利用者の都合で好き勝手にいじり回せるオブジェクト。

仕組みとしては RString の組み込みデータと同じ。ただしデータ長は変更できない。

include/mruby/istruct.h に記述のある通り、インスタンス変数の保持は不可。

mrb_value を3ワード分のメモリ領域に置いたとしても GC のマーク対象外になる。

ファイナライザは利用不可。

/* mrb_obj_alloc() を使って生オブジェクトを生成 */
struct RIstruct *objp = (struct RIstruct *)mrb_obj_alloc(mrb, MRB_TT_ISTRUCT, 任意のクラス);

/* inline_data は char の配列として定義されている */
memset(o->inline_data, -1, sizeof(o->inline_data));
/* include/mruby/istruct.h */
#define ISTRUCT_DATA_SIZE (sizeof(void*) * 3)
struct RIstruct *RISTRUCT(mrb_value obj); /* 実体はマクロ */
char *ISTRUCT_PTR(mrb_value obj);         /* 実体はマクロ */
MRB_INLINE mrb_int mrb_istruct_size(void);
MRB_INLINE void *mrb_istruct_ptr(mrb_value object);
MRB_INLINE void mrb_istruct_copy(mrb_value dest, mrb_value src);

ハッシュ (Hash)

ハッシュオブジェクトの生成

mrb_value mrb_hash_new(mrb_state *mrb);
mrb_value mrb_hash_new_capa(mrb_state*, int capacity);

例外処理・大域脱出 (💣)

/* 1.4.0:include/mruby.h */
MRB_API struct RClass * mrb_exc_get(mrb_state *mrb, const char *name);
MRB_API mrb_value mrb_exc_new(mrb_state *mrb, struct RClass *c, const char *ptr, size_t len);
MRB_API mrb_noreturn void mrb_exc_raise(mrb_state *mrb, mrb_value exc);
extern struct RClass *E_RUNTIME_ERROR;      /* 実体はマクロ */
extern struct RClass *E_TYPE_ERROR;         /* 実体はマクロ */
extern struct RClass *E_ARGUMENT_ERROR;     /* 実体はマクロ */
extern struct RClass *E_INDEX_ERROR;        /* 実体はマクロ */
extern struct RClass *E_RANGE_ERROR;        /* 実体はマクロ */
extern struct RClass *E_NAME_ERROR;         /* 実体はマクロ */
extern struct RClass *E_NOMETHOD_ERROR;     /* 実体はマクロ */
extern struct RClass *E_SCRIPT_ERROR;       /* 実体はマクロ */
extern struct RClass *E_SYNTAX_ERROR;       /* 実体はマクロ */
extern struct RClass *E_LOCALJUMP_ERROR;    /* 実体はマクロ */
extern struct RClass *E_REGEXP_ERROR;       /* 実体はマクロ */
extern struct RClass *E_FROZEN_ERROR;       /* 実体はマクロ */
extern struct RClass *E_NOTIMP_ERROR;       /* 実体はマクロ */
extern struct RClass *E_FLOATDOMAIN_ERROR;  /* 実体はマクロ */
extern struct RClass *E_KEY_ERROR;          /* 実体はマクロ */
extern struct RClass *E_FIBER_ERROR;        /* 実体はマクロ */

/* 1.4.0:include/mruby/error.h */
struct RException *mrb_exc_ptr(mrb_value);  /* 実体はマクロ */
MRB_API void mrb_sys_fail(mrb_state *mrb, const char *mesg);
MRB_API mrb_value mrb_exc_new_str(mrb_state *mrb, struct RClass* c, mrb_value str);
mrb_value mrb_exc_new_str_lit(mrb_state*, struct RClass*, const char *);  /* 実体はマクロ */
MRB_API mrb_value mrb_make_exception(mrb_state *mrb, mrb_int argc, const mrb_value *argv);
MRB_API mrb_value mrb_exc_backtrace(mrb_state *mrb, mrb_value exc);
MRB_API mrb_value mrb_get_backtrace(mrb_state *mrb);
MRB_API mrb_noreturn void mrb_no_method_error(mrb_state *mrb, mrb_sym id, mrb_value args, const char *fmt, ...);
MRB_API mrb_value mrb_f_raise(mrb_state*, mrb_value);

関連するヘッダに include/mruby/throw.h があるが、これは内部実装用であるため触るべきではない。

これに関して @take_cheeze さんからコメントの引用を許可頂きました。ありがとうございます。

mruby/throw.h は内部実装用のヘッダなので、C++と使う予定があるなら絶対に使わないでください。
C側では必ず mrb_exc_raise などで例外をとばし、 mrb_protect で例外を捕捉してください。
mruby/throw.h を使っている mrbgem は移植性が低いので個人的に非推奨扱いです。

例外を発生させる

/* include/mruby.h */
MRB_API mrb_noreturn void mrb_raise(mrb_state *mrb, struct RClass *c, const char *msg);
MRB_API mrb_noreturn void mrb_raisef(mrb_state *mrb, struct RClass *c, const char *fmt, ...);

例外クラスとテキストメッセージを指定するだけならば mrb_raise() で十分。

文字列に任意の値を埋め込みたい場合は mrb_raisef() を使う。ただし rb_raise とは異なり、printf フォーマットは mruby オブジェクトを意味する %S のみが有効であることに注意。

mrb_value obj1 = ...;
mrb_value obj2 = ...;
mrb_raisef(mrb, E_RUNTIME_ERROR, "error for %S and %S", obj1, obj2);

例外の対応処理をするには

mruby-error パッケージが必要。

/* 1.4.0:include/mruby/error.h */
MRB_API mrb_value mrb_protect(mrb_state *mrb, mrb_func_t body, mrb_value data, mrb_bool *state);
MRB_API mrb_value mrb_ensure(mrb_state *mrb, mrb_func_t body, mrb_value b_data, mrb_func_t ensure, mrb_value e_data);
MRB_API mrb_value mrb_rescue(mrb_state *mrb, mrb_func_t body, mrb_value b_data, mrb_func_t rescue, mrb_value r_data);
MRB_API mrb_value mrb_rescue_exceptions(mrb_state *mrb, mrb_func_t body, mrb_value b_data, mrb_func_t rescue, mrb_value r_data, mrb_int len, struct RClass **classes);
  • mrb_protect() は利用者によって与えられた body 関数で発生した例外 (大域脱出) を捕まえて、戻り値として返す。
  • mrb_ensure() は利用者によって与えられた body 関数を抜けた場合に必ず ensure 関数を呼ぶ。
  • mrb_rescue() および mrb_rescue_exceptions() は利用者によって与えられた body 関数で例外が起きた場合 rescue 関数を呼ぶ。

特定の例外だけを握りつぶしつつ共通の後始末を行いたい

mruby-error mrbgem パッケージが必要。

mrb_ensure()mrb_rescue_exceptions() を組み合わせる。

二つの関数を組み合わせた関数 protect_exceptions() のサンプルコードは次の通り。

struct protect_exc_try
{
  mrb_func_t body;
  const mrb_value *b_data;
  mrb_func_t rescue;
  const mrb_value *r_data;
  mrb_int len;
  struct RClass **classes;
};

static mrb_value
protect_exc_try(mrb_state *mrb, mrb_value arg)
{
  struct protect_exc_try *p = (struct protect_exc_try *)mrb_cptr(arg);

  return mrb_rescue_exceptions(mrb, p->body, *p->b_data, p->rescue, *p->r_data, p->len, p->classes);
}

mrb_value
protect_exceptions(mrb_state *mrb,
                   mrb_func_t body, mrb_value b_data,
                   mrb_func_t rescue, mrb_value r_data,
                   mrb_func_t ensure, mrb_value e_data,
                   mrb_int len, struct RClass **classes)
{
  if (ensure) {
    if (rescue) {
      struct protect_exc_try arg = { body, &b_data, rescue, &r_data, len, classes };

      return mrb_ensure(mrb, protect_exc_try, mrb_cptr_value(mrb, &arg), ensure, e_data);
    } else {
      return mrb_ensure(mrb, body, b_data, ensure, e_data);
    }
  } else if (rescue) {
    return mrb_rescue_exceptions(mrb, body, b_data, rescue, r_data, len, classes);
  } else {
    return body(b_data);
  }
}

mrb_protect() で大域脱出を捕捉し、後処理を終えたら再搬送したい (💣💣💣💣💣)

あぼーん

mrb_rescue()mrb_rescue_exceptions() で再搬送させるようなことは出来ない (💣💣💣💣💣)

あぼーん

テスト

テスト環境のみで利用可能な FIXNUM_MAXFIXNUM_MINFIXNUM_BITFLOAT_TOLERANCE 定数が用意される。

テストコードのファイルは test/ に書いておくと、mruby のビルドスクリプトが勝手に探ってくれるようになっている。

test/*.rb では主に次のメソッドを使う (git grep 'def assert' test/assert.rb が役立つ)。

  • assert
  • assert_true
  • assert_false
  • assert_equal
  • assert_not_equal
  • assert_nil
  • assert_include
  • assert_not_include
  • assert_raise
  • assert_nothing_raised
  • assert_kind_of
  • assert_float

test/*.c には src/*.c と同様に色々出来る。ただし void mrb_XXX_gem_test(mrb_state *mrb) 関数がテスト前に呼び出されるようになっている。

mrbgem.rake でさわれるテスト時の設定は次の通り。

MRuby::Gem::Specification#add_test_dependency
MRuby::Gem::Specification#test_objs
MRuby::Gem::Specification#test_rbfiles
MRuby::Gem::Specification#test_args
MRuby::Gem::Specification#test_preload

トラブルシューティング・バッドノウハウ

MRB_INT16 + conf.enable_test で作成した bin/mrbtest が失敗する

$ host-int16/bin/mrbtest
mrbtest - Embeddable Ruby Test

ArgumentError: string (10000000) too big for integer
zsh: abort      host-int16/bin/mrbtest

みたいな状況にある場合、mruby-bin-mrbc を追加してみる。

MRuby::Build.new("host-int16") do |conf|
  conf.cc.defines = %w(MRB_INT16)
  conf.gem core: "mruby-bin-mrbc"
  conf.gem core: "mruby-bin-mruby"
  conf.enable_test
end

MRB_NAN_BOXING を定義して構築した bin/mruby が SEGV や ABORT を起こす、

build_config.rbmruby-bin-mrbc を追加してみる。

MRuby::Build.new("host-nan-boxing") do |conf|
  conf.cc.defines = %w(MRB_NAN_BOXING)
  conf.gem core: "mruby-bin-mrbc"
  conf.gem core: "mruby-bin-mruby"
end

mrb_int の範囲で処理を分ける時、MRB_INT16 や MRB_INT64 と #if defined で行わない

MRB_INT_MAXMRB_INT_MIN で行うべき。

これは、MRB_WORD_BOXING が定義されている時の予防となる。
この場合、整数値の有効ビットが C よりも1ビット少なくなるため、#if defined に頼ると値の桁落ちが発生して不可解なエラーに悩まされることにつながる。

追加した mgem の include ディレクトリが探索対象とならない場合の対策

build_config.rbconf.gem した mgem は include パスに含まれない。

mrbgem.rakespec.add_dependency を指定することで、その mgem 内でのみ利用可能となる。

非公開・未公開 mgem を add_dependency で指定できない場合の対策

mrbgem.rakespec.add_dependency で指定可能なのは github や bitbucket で git リポジトリに置かれたもの、mgem-list に登録されたもの、build_config.rbconf.gemconf.gem によって指定したものとなる。

そのため、ローカルディレクトっリのみにある mgem を指定したいならば、build_config.rbconf.gem で パッケージのディレクトリを指定し、依存する mgem の mrbgem.rakespec.add_dependency でそのパッケージ名を指定する。

gem core: "mruby-print" したのに Kernel#printf が動作しない

build_config.rb に記述された gem core: "mruby-print"前へ gem core: "mruby-sprintf" を追加する。

build_config.rb
MRuby::Build.new do |conf|
  gem core: "mruby-sprintf"
  gem core: "mruby-print"
end

mruby-sprintfmruby-print の順番が重要

逆になっていると悲しいことが起きる。

悲劇が起きる設定例:

build_config.rb
MRuby::Build.new do |conf|
  gem core: "mruby-print"   ## 順番が逆!
  gem core: "mruby-sprintf"
end

悲劇:

### print + sprintf は動作するのに……
$ host/bin/mruby -e 'print sprintf("%d\n", 100)'
100

### printf は動作しない!
$ host/bin/mruby -e 'printf("%d\n", 100)'
trace (most recent call last):
    [0] -e:1
-e:1: printf not available (NotImplementedError)

未整理

mrb_rd_data_p ってなんぞや?

/* 1.4.0:include/mruby/value.h */
static inline mrb_bool mrb_ro_data_p(const char *p); /* 通常では実体がマクロ */

渡されたポインタが static const であるかどうかを確認する関数。通常では mrb_ro_data_p() は偽を返すだけ。

1.4.0:include/mruby/value.h を見ると、conf.defines << "MRB_USE_ETEXT_EDATA" した時に、テキストセグメントに含まれていれば真を返す関数となる (必要であれば conf.defines << "MRB_NO_INIT_ARRAY_START" を定義することが出来る)。

全ての環境でそのまま利用できるわけではない3

mrb_str_new_cstr() / mrb_intern_cstr() にリテラル文字列を渡しても mrb_str_new_lit() / mrb_intern_lit() と同じように扱われるため、特に小さい組み込み機器で mruby を動作させたい場合は (必要であれば _etext の独自実装と共に) 積極的に使っていくと美味しいかもしれない。

書式文字列

/* include/mruby.h */
MRB_API mrb_value mrb_format(mrb_state *mrb, const char *format, ...);

フォーマット指定子は mruby のオブジェクトを意味する %S のみが利用できる。

どうしても sprintf みたいな書式を使いたい

mruby に付属の mruby-sprintf は mruby 空間のみでしか利用できない。

そのため mrb_funcall() 経由で Kernel#sprintf を呼び出す。

mrb_value format = mrb_str_new_cstr(mrb, "%s, %d");
mrb_value str = mrb_str_new_cstr(mrb, "ほにゃらら");
mrb_value num = mrb_fixnum_value(123456789);
mrb_value str;
str = mrb_funcall(mrb, mrb->object_class, mrb_intern_cstr(mrb, "sprintf"), 3, format, str, num);

直接関数を呼び出す (💣💣💣)

おい見ろよ! mrbgems/mruby-sprintf/src/sprintf.c で定義されている mrb_str_format() 関数は static 修飾されてねえぞ! なんて都合がいいんだ!

プロトタイプ宣言がされたインクルードファイルがないから、自分で宣言しちまえば利用できるじゃん!

/* mrbgems/mruby-sprintf/src/sprintf.c */
mrb_value mrb_str_format(mrb_state *mrb, mrb_int argc, const mrb_value *argv, mrb_value fmt);

本来の sprintf() のような可変長引数ではないし va_list も使えないため完全に置き換えられないが、それなりに事足りそうだ。

怒られないように、バレないように、こっそり使おう (💣💣💣)。

REFERENCES

LICENSING


  1. 即値は GC 対象にはならない。また、GC の対象外であり、かつアリーナを消費することもない。ただしクラスは固定されるため、シングルトンクラスを持つことが出来ない。 

  2. main 関数などの引数の並び (argc, argv) とは逆になっている (&argv, &argc) ので注意。 

  3. mingw32 (Windows 向け) でコンパイル・リンクを試したら _etext シンボルが存在しないとしてリンクエラーとなった。 

14
4
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
4