Dec 31, 2007

coerceとか代入演算子とか


数学関数をお題にして、拡張ライブラリの制作方法を勉強中。以下、今日知っ たことのメモです。昨日の分と同様、RVec4は「4次元ベクトルを表すクラス」 として読んでください。

coerce

たとえば v * 2.0 と書いたなら RVec4#* が利用されるが、 2.0 * v だとまず Float#* が使われる。しかし Float は RVec4 というクラスについて知らない。
このような場合、Ruby では v.coerce(self) というメソッドを介して RVec4 に自身の型変換を依頼し、その結果を元に演算を続行する:
a, b = v.coerce(self)
a * b
自分のクラスで coerce を導入するならば、
rb_define_method( rb_cRVec4, "coerce", RVec4_coerce, -1 );
…のように初期化して、相手方に応じて適切な結果を配列で返す:
static VALUE RVec4_coerce( int argc, VALUE* argv, VALUE self )
{
...
switch( TYPE(argv[0]) )
{
case T_FLOAT:
case T_FIXNUM:
case T_BIGNUM:
    /* RVec4 (op) argv[0] の形式で再計算を試みる */
    return rb_ary_new3( 2, self, argv[0] );
...
計算中に何度もこのような型変換が発生してしまうのはできる限り避けたいと ころ。かといって coerce を正しく書いておかないと、無意味な式が通ってし まう可能性がありますね( "hoge"+vとか書かれたら? )。無用な coerce がなるべく発生しないように気を付けるしかないのかもしれません。
参考:

代入演算子

@を適当な演算子だとすると、
式1 @= 式2
という式は
式1 = 式1 @ 式2
として扱われる。このため、たとえば
rb_define_method( rb_cRVec4, "+",  RVec4_op_binary_plus, -1 );
rb_define_method( rb_cRVec4, "+=", RVec4_op_assign_plus, -1 );
のように初期化していたとしても、 v1 += v2 という記述で RVec4_op_assign_plus が利用されることはない。
上記の RVec4#+ も該当しますが、元となる2項演算子がオブジェクトを 生成するようなものだとすると、実行時のパフォーマンスは悲惨なものになり そうですね。代入演算子は利用せずに、別な名前で明示的に利用することにな るようです。
Yoshiさんによる math3d では メソッドが破壊的であることを示す 記号を使って、
void
Init_math3d()
{
...
  rb_define_method(cVector, "add!", rb_vec_add, 1);
…と、わかりやすい名前が付けられていました。
参考:

Dec 30, 2007

拡張ライブラリ


mkrf も ruby-opengl 0.60.0 もアップデート来ませんね。しかたがないので、Ruby の拡張ライブラリの作り 方を勉強しながら待つことに(笑
SWIGを試してみたことはありますが、1から 書いたことがなかったので。
お題は数学関数に。うまいこと完成したら、ruby-opengl と一緒に使えるかも しれません。
以下のメモに出てくるRVec4は「4次元ベクトルを表すクラス」として 読んでみてください。

クラスの追加

rb_cRVec4 = rb_define_class( "RVec4", rb_cObject );
rb_cObject は include/ruby/ruby.h で宣言されている組み込みの変数。

メモリ確保関数の指定

rb_define_alloc_func( rb_cRVec4, RVec4_allocate );
RVec4.newと書くと、allocateinitializeの順にメソッド が走るようです。このallocateのときに呼ばれる関数は上記のように指 定できるようになっています。

確保した領域の Ruby オブジェクト(T_DATA)化

RVec4* v = malloc( sizeof(RVec4) );
...
VALUE obj = Data_Wrap_Struct( rb_cRVec4, 0, RVec4_free, v );
C++ でいうPlacement new みたいなものかな? obj の型は TYPE(obj) == T_DATA だった。 この領域を解放する関数は Data_Wrap_Struct の第3引数で指定する。

インスタンスメソッドの追加

rb_define_method( rb_cRVec4, "getLength", RVec4_getLength, -1 );
これで RVec4 クラスのインスタンスに getLength メソッドが追加される。実装は
VALUE RVec4_getLength( int argc, VALUE* argv, VALUE self );
という関数で行う。

クラスメソッド*1の追加

rb_define_singleton_method( rb_cRVec4, "dot", RVec4_dot, -1 );
これで result = RVec4.dot(v1, v2) という感じで利用できることになる。

Ruby の VALUE から C の構造体へのキャスト

VALUE RVec4_getLength( int argc, VALUE* argv, VALUE self )
{
    RVec4* v = NULL;
    ...
    Data_Get_Struct( self, RVec4, v );
    ...

float/doubleのT_FLOAT化

VALUE obj = rb_float_new( flt );

Ruby のメソッドを使う

obj の to_s メソッドを使いたいときは:
VALUE str = rb_funcall( obj, rb_intern("to_s"), 0 );
…となる。 rb_funcall の第3/第4引数にはそれぞれ argc, argv を渡すことができる。
特に rb_funcall がおもしろいですね。Ruby の機能がそのまま C から利用できるというわけですから。
*1  正確には「クラス(を表すオジェクト)に対する特異メソッド」と呼ぶらしい。

Dec 28, 2007

Mesa-7.0.2 : リンクエラー


MesaLib-7.0.2 を VC++2005 でビルドしようとしたら、こんなリンクエラーが出た:
2>   ライブラリ .\Release/OPENGL32.lib とオブジェクト .\Release/OPENGL32.exp を作成中
2>mesa.lib(glapi.obj) : error LNK2001: 外部シンボル "_gl_dispatch_stub_772" は未解決です。
2>Release/OPENGL32.DLL : fatal error LNK1120: 外部参照 1 が未解決です。
修正方法の指針はここにあった:
  • Nabble - mesa3d-users - Compile problem
具体的には以下の通り。
Mesa-7.0.2/src/mesa/drivers/windows/gdi/wmesa.c
に、1536行目として
void gl_dispatch_stub_772(void){}
と書き加えてビルドすればOK。
diff -c c:/Mesa-7.0.2/src/mesa/drivers/windows/gdi/wmesa.c\~ c:/Mesa-7.0.2/src/mesa/drivers/windows/gdi/wmesa.c
*** c:/Mesa-7.0.2/src/mesa/drivers/windows/gdi/wmesa.c~ Fri Nov  2 00:30:52 2007
--- c:/Mesa-7.0.2/src/mesa/drivers/windows/gdi/wmesa.c Fri Dec 28 19:06:29 2007
***************
*** 1533,1537 ****
--- 1533,1538 ----
  void gl_dispatch_stub_769(void){}
  void gl_dispatch_stub_770(void){}
  void gl_dispatch_stub_771(void){}
+ void gl_dispatch_stub_772(void){}

  #endif

Diff finished.  Fri Dec 28 19:13:18 2007

TRAMP で FTP


TRAMP に FTP を使ってアクセスしてもらうには、明示的にプロトコル名を書かなけれ ばいけないらしい。
Emacs のミニバッファには次のように書くことになる:
Find file: /ftp:username@server.address:/path/to.file