今さら感漂うが Google V8 JavaScript Engine を使ってみた

2009/08/31 | めも

D 言語上から JavaScript 使いたいでござると試行錯誤した記録*1.SpiderMonkey もトライしたんだが結局 V8 に.

説明の順序がアレな気もするけど Google V8 JavaScript Engine っていうのは名前にもあるとおり JavaScript エンジンで,Chrome に採用されてるやつ.オープンソースなのでフリーに使うことができて,組み込むことでアプリケーションに JavaScript を解釈する機能を持たせることができる.

この手のモジュールってオプソとは言いつつも自社製品特化のためとかなのかなんなのか,ごにょごにょだったりして使い勝手がいまいちで,自アプリに便利に組み込んで使えるという地点からかけ離れていることが少なからずあるんだけど,さすが Google というべきか,

  • 言語 C++
  • 気の利いたビルド環境
  • 導入ドキュメントばっちり*2
  • 隠蔽度が必要十分
  • 他外部ライブラリに非依存
  • ライセンスは修正 BSD

と十分に使いやすいものになっていた.

参考ページ

できたこと

  • 簡易インタフェースを C++ で書いて共有ライブラリ化. D からそれを呼ぶ.
  • 実行コンテクストの切り替えや js の評価と結果の取得とか基本的なこと.
  • D の関数を js にエクスポート(js から D の関数を呼べる).
  • tsuikyo.js を実際に D から使ってみるなど.

まだだけどやりたいこと

  • ラッパをなんとかして D のオブジェクトの js エクスポートを楽に.これ的な.
  • 拡張を考えて実装してみる (誰でも考えるであろう include(’hoge.js’); とか).
  • ゲーム用スクリプトエンジンとして js 使えるようなフレームワーク考える.

ぐだぐだメモ

組み込みスクリプト言語が必要なほどの規模の開発してこなかったのでこういうの初めて.

で,まず言語チョイスに悩んだんだよね.

Python なり Ruby なりでいいんじゃね?

という雰囲気はあるにしろさすがにおどろおどろしくリアルデカい気がする*4.でもって,じゃあ Lisp でバッチリ! と思えるほど頭が良くない.

D にゲーム向け組み込みスクリプト言語?

ときたら Lua でしょう!

みたいな前例におとなしく従うというのもいつも通り天の邪鬼なもので気が向かなかったので,マイナーどころ探しの旅に二瞬ばかり出かけたんだ

けど,我に返って実用性現実性汎用性と呟いて,

多くの人が知ってて情報が溢れてて(表面的には)ヌルく書ける言語がいい

という案外に安直な方針に固まって,JavaScript を検討しだした.検討しだすと tsuikyo も js 版だけメンテすれば使えるし一石二鳥だなとか個人的な都合も味方していいじゃんいいじゃんとなった.

D は C/C++ なインタフェースとは接合性があるので,C/C++ 使った実装を探すと SpiderMonkey と V8.

始め SpiderMonkey を Windows OS 環境でビルドしようとしたが,スムースに行かず,ぐぐっても情報が錯綜してる印象.利用する際のコードもあんまり美しくない*5模様.あとライセンスに GPL 入ってる.

そんなわけで V8 に目を移すと上記不満点がほとんど解消されている.

svn リポジトリからチェックアウトして,scons を入れたら Windows でも一発ビルド. Debian も問題なし. 抽象化されたスマートなコード.されすぎていてテンプレート慣れしてない俺が面食らったのは内緒.

次いで D からのリンクをどうするか.

C だったら D が完全なインタフェース持ってるのでもうそのまんまオブジェクトコードリンクして使えたんだけど,C++ でかつテンプレートバリバリでとてもそのままじゃ D から呼べなさげ.

なのでインタフェース部を素直な C++ で書くことにする.そいつを共有ライブラリにしてやれば D から ABI インタフェースで呼べる.

しかし STL から逃げ出したため C++ ろくに使えませんという残念な身としてはかなり苦戦.サンプルの shell.cc を参考にがんばる.

js のグローバルオブジェクトだの実行コンテクストだのといった知識に助けられて少しずつコードの意味が理解できてくる.

さらに現場百回ではないけど,コードと公式ドキュメントを 100 回くらい往復してようやく全貌が掴めてきて,単体で動くコードにたどり着く.

が,Debian では無問題だが Windows での DLL 作成とリンクに嵌る.慣れてないことをするとすぐ嵌る.

コンパイラが吐くオブジェクトファイルの形式について(古い COFF,新しい COFF,OMF)知識がなかったことが原因で,この辺を参考にして試行錯誤.

要は msvc が吐く lib ファイルは新しい COFF 形式だから OMF 対応の dmd から使えないと.でもって OMF な obj が吐ける dmc では全然コンパイル通らないのであった.

正解は DLL から lib を作る方法で,Digital Mars 製の implib*6を使って lib を作って解決.

cl /LD v84d.cpp v8.lib winmm.lib ws2_32.lib

implib v84d.lib v84d.dll

dmd hoge.d v84d.lib

とか.

そこで詰まった分だけあとは勢いづいて,D で C++ のインタフェースをラップするクラスを書いて, Hello World 実行.おk.

調子に乗って

string s = q"JS
    var e = function(s){return eval(s);};
    var token = [
                    ["var", "ほげ", "=", "10", ",", "はげ", "=", "0"],
                    ["while", "(", "ほげ", "=", "ほげ", "-", "1", ")", "はげ", "=" ,"はげ", "+", "ほげ"],
                    ["はげ"]
    ];

    var exp = [];
    for(var i = 0; i < token.length; i++){
        exp.push(token[i].join(" "));
    }
    exp.push("");

    "答 " + e(exp.join(";"));
JS";

V8D v8d = new V8D();
writefln(v8d.eval(s));

とか書いてみるけど正しく動く(”答 45″ が出力される). D がいろいろ utf8 推奨で, V8 も utf8 で解釈してるようなので,この辺は都合いい.

どうでもいいけどこういう入れ子構造のソースをそれぞれ適切にハイライトしてくれるハイライタってあるのかね.適用部分と言語は明示的に指定するんでいいからさ.外側の言語の文字列定数の色と,内側の言語の各部分の色をブレンドした色で内側がハイライトされたりするとかっこいいと思うんだけど.

で,これだけだと D -> js は操作できても逆が無理なので,逆もがんばる.関数のエクスポート.

C++ に普通に組み込むだけなら各所のサンプルにある通りなんだけど,D の関数を js エンジンに登録するにはどうするか,って関数ポインタでコールバックしか思いつかないんでそれで.

D の callback 関数に extern(C) をつけて呼び出し規約を合わせ,&callback としたポインタを C++ インタフェースに投げる. D の関数ポインタはなんかオブジェクトになってた気がするけど void* に暗黙キャストされるみたい*7

むしろ C++ インタフェース側が問題.V8 に関数ポインタをそのままコールバックとして登録することはできないので,代理の静的な関数を登録しておいて,その関数から保存しておいた関数ポインタを呼び直す,ということを考えた.

ポインタどう保存するべきかについて考えると閃いて,というか先人の知恵が生きて,V8 の管理してるオブジェクトの中に v8::External な型にラップして適当な名前でポインタを埋めておけばうまくいくとわかった.

FunctionTemplate に Set で埋め込んで,登録した静的なコールバック関数では args.Callee(); でその FunctionTemplate (なのかな?)を取得,Get して v8::External にキャストしてアンラップして元のポインタの値を取り出し,適切な型(これも埋めておく)に static_cast して呼び出し.

スムースにできたように書きつつも Access Violation を量産してようやっと動く. js から引数付きの D 関数が呼べるし,その返値は js から受け取れる.おk.

stdout に出力する D 関数 print() を同名で js のグローバルに登録して,tsuikyo_js を試してみる.

string initJS = q"JS
       var ts = new Tsuikyo();
       var w = ts.add("ついきょう");
JS";

string refreshJS = q"JS
       var k = w.getKeyString();
       var p = w.getKeyPos();
       print(k.substr(0, p) + "_" + k.substr(p));
       p == k.length;
JS";

V8D v8d = new V8D();
v8d.register("print", &print, "int (char*)");
v8d.load("tsuikyo.js");
v8d.eval(initJS ~ refreshJS);

char c;
while(c != 27 && c != 10 && c != 13){
    if(kbhit() && (c = getch()) > 31){
        v8d.eval("w.stroke('" ~ c ~ "');");
        if(v8d.eval(refreshJS) == "true"){
            break;
        }
    }
}

これで超簡易タイピングアプリ. tsuikyo 程度のものはスクリプト上で動いていても速度的に困らないはず. V8 速いし!

まあこういう使い方あんまり宜しくないけど,コード見て楽しくはあるよねっていう. 高度の全然違う二物体を上空から見下ろすと一つの図形を描いて見える,みたいな芸術的感性を刺激される感じが.

規模がでかくなければこの程度扱えればなんとかなるので一段落か.

でもクラス丸ごとエクスポートの方法とか心得ておくと後々幸せだろうなぁ.

割と楽しめるので物好きな人は V8 組み込みやってみるといいんではないかと思います.

と無意味にベタに締めるのであった.

  1. がっつり何かを作る時間がないから,こうやって外堀を埋めていく毎日. []
  2. ただし一歩進むと一気に情報量は減る. []
  3. すげーな誰のページだろ,と思ったら tossy-2 さんかよっていう(すごく遠いけど知ってるっちゃ知ってる人) []
  4. 試してないので偏見かも. []
  5. 実装が C だからしょうがない部分ではある. []
  6. dm/bin に入ってる.早く気付けよ……. []
  7. @v2.014 []

Trackback URL

この記事にはまだコメントがついていません。

コメントをどうぞ