nazonoRubyist RubyでJavaScriptのためにC このページをアンテナに追加 RSSフィード

2006-06-27

Windowsで動かしている人が! Windowsで動かしている人が! - nazonoRubyist RubyでJavaScriptのためにC を含むブックマーク はてなブックマーク - Windowsで動かしている人が! - nazonoRubyist RubyでJavaScriptのためにC Windowsで動かしている人が! - nazonoRubyist RubyでJavaScriptのためにC のブックマークコメント

http://rubyist.g.hatena.ne.jp/miyamuko/20060627/

tmp\ruby-smjs>ruby test.rb
Loaded suite test
Started
......E......E
Finished in 0.188 seconds.

  1) Error:
test_defclass(SpiderMonkeyTest):
NoMethodError: undefined method `bindClass' for #<SpiderMonkey::Context:0x2971f1
8>
    test.rb:188:in `test_defclass'

  2) Error:
test_wikiparser(SpiderMonkeyTest):
Errno::ENOENT: No such file or directory - WikiParser.js
    test.rb:16:in `initialize'
    test.rb:16:in `test_wikiparser'

14 tests, 65 assertions, 0 failures, 2 errors

おお! 動いてますね。テストの通ってないのは僕の手元と同じで、仕様です(汗)。WikiParser.js がないのはおなじディレクトリ

 wget http://dev.ishinao.net/WikiParser/javascript/WikiParser.js

とでもすれば、多分大丈夫。

Mingwの方はよくわかりません! 終了処理で失敗しているのか?JS_DestroyRuntimeが呼ばれない、some situation?それとも、JSContextにSetContextPrivateでルビーオブジェクトを結びつけているのが悪いのかな?


Ruby/SpiderMonkeyビルド

VC++ だと大量のエラー (それも理不尽な) が出るので以下のパッチを当てる。

エラーの原因は以下の通り。

パッチは手作業で(汗)取り込みました。コメントは最後に半角スペースを入れることにしたので、多分そのままで通るようになったと思います。手元の環境だと _alloca が失敗したので、jargv[argc-1] はjargv[argc]と一個多めに確保することにしました(--;)

ありがとうございます


しかしいつ駄目になるかはわからない罠…コンパイルオプションか何かでチェックできればいいんだけど。

関数の設定と実行 13:48 関数の設定と実行 - nazonoRubyist RubyでJavaScriptのためにC を含むブックマーク はてなブックマーク - 関数の設定と実行 - nazonoRubyist RubyでJavaScriptのためにC 関数の設定と実行 - nazonoRubyist RubyでJavaScriptのためにC のブックマークコメント

  v = SpiderMonkey::eval("v={}")
  v.function :hoge, { |a|
    a + 1
  }
  assert_equal 3, v.call(:hoge, 2 )

こんな感じのインタフェースで動くようにしよう。

関数を定義する関数

// Ruby関数をJavaScriptに登録する
VALUE
rb_smjs_value_function( int argc, VALUE *argv, VALUE self ){
  // 引数の解析
  VALUE proc, name;
  rb_scan_args(argc, argv, "1&", &name, &proc );
  jsval value=RBSMValue_TO_JSVAL( self );
  JSContext *cx= RBSMValue_TO_JsContext( self );
  char *cname = StringValuePtr(name);

  // 関数をJS側のオブジェクトに設定し、呼ばれたら rb_smjs_value_function_callback が
  // 呼ばれるようにする
  JSFunction* fun = JS_DefineFunction( cx, 
      JSVAL_TO_OBJECT(value), cname, 
      rb_smjs_value_function_callback, 0, 0 );
  if(!fun) rb_raise( eJSError,"js define function" );

  // 関数のプロパティーに、procへのポインタを持つオブジェクトを設定
  JSObject* fobj=JS_GetFunctionObject(fun);
  JSObject *pobj = JS_NewObject( cx, NULL, NULL, fobj );
  JS_SetPrivate( cx, pobj, (void *)proc );
  JS_DefineProperty( cx, fobj, "__ruby_funcion_", OBJECT_TO_JSVAL(pobj),
      NULL,NULL,JSPROP_PERMANENT | JSPROP_READONLY );

  return proc;
}

JSObjectにはJS_SetPrivateで自由にvoid*型の(つまりなんでも)ポインタを設定できる。で、関数オブジェクトに設定しようとしたところ、なぜかセグメンテーション違反が出てしまった。ので、関数オブジェクトに謎のプロパティーを設定し、そのプロパティー=JSObjectに、procへのポインタを渡すようにした。これって、GCとかでprocが消えちゃったり移動したりしないことが前提の設定だなぁ。まあいい。

これによって、設定した関数JavaScriptから呼び出されたときは rb_smjs_value_function_callback が呼び出される。その実装はこうなっている。

// 登録したRuby関数がJavaScriptから呼ばれた
static JSBool
rb_smjs_value_function_callback(
    JSContext *cx, JSObject *thisobj, 
    uintN argc, jsval *argv, 
    jsval *rval){
  // 呼び出された関数をオブジェクトとして取り出す
  JSFunction *fun = JS_ValueToFunction( cx, argv[-2]);
  JSObject *fobj = JS_GetFunctionObject(fun);

  // 関数オブジェクトに仕込んでおいたプロパティーからProcを取り出す
  jsval rfuncval;
  JS_GetProperty( cx, fobj, "__ruby_funcion_", &rfuncval);
  JSObject *obj = JSVAL_TO_OBJECT(rfuncval);
  VALUE proc = (VALUE)JS_GetPrivate(cx,obj);

  // JSContextに仕込んでおいたプライベート値からContextを取り出す
  VALUE context = (VALUE)JS_GetContextPrivate(cx);

  // 引数をSpiderMonkey::Valueに。
  // 引数の最後にprocをくっつける
  VALUE rargs = rb_ary_new2( argc + 1);
  uintN i;
  for( i=0; i<argc; i++ ){
    rb_ary_store( rargs, i, rb_smjs_value_new_jsval( context, argv[i] ) );
  }
  rb_ary_store( rargs, i, proc );

  // proc を実行
  int status;
  VALUE res = rb_protect( rb_smjs_ruby_proc_caller, rargs, &status );

  // 例外が投げられた、等の場合
  if( status != 0){
    return rb_smjs_raise_js( cx, status );
  }

  // 返値をJavaScriptオブジェクトに変換
  return rb_smjs_ruby_to_js( cx, res, rval );
}

いくつかポイントが。あるが、それはまた明日。