V8 (Google JavaScript Engine) を embed した感想とかあれこれ
なぜ CodeRepos に登録しないのか
べつだん深意や確執があるわけじゃなくて,華々しく 500 人めのコミッタになろうと思ったら,現在 461 人だったからです。ということで 38 人の方々,コミッタ登録してください ;P
冗談はともかくおまえの書いた汚いコードを早く添削したいんじゃという方がいらっしゃったら,代理でいれといて構いません。
Acme::JavaScript::V8
(Perl XS)を書くときに苦労したこと
New
ていうのが XS での define 値だったので困りました。V8 側だと,new
/ delete
するんじゃなくて Class::New()
する流儀なので。
ですから,#undef New
してあります。他のマクロで使われていたらマズいなぁと思いますが,動いたからよしとします。
エンベッダーズガイド
- V8 JavaScript engine を読む
samples/shell.cc
を読む- fcgi-v8 や twitter-v8 を読む
- V8 JavaScript engine を読む
- 必要に応じて
samples/process.cc
を読む- 全体像(何をするためのプログラムか)を把握する必要はない
Handle<T>
とか Local<T>
とか Persistent<T>
とか
Handle<T>
とかっていうのは,いわゆるスマートポインタ的なものです。
なお Handle<T>
が抽象クラスで,Local<T>
と Persistent<T>
はそれらのサブクラスになります。だからたいていの場合,自作関数の戻り値は,Handle<T>
にしとけばいいでしょう。
Local<T>
は HandleScope
によって自分でスコープを指定できる auto_ptr
的なものです。実際には参照されているかどうかも加味してくれる(被参照がなくなれば GC される)ので,関数(スコープ)抜けたらなくなるのかー,とビビる必要はありません。たぶん。
Persistent<T>
は,いちど V8 の世界を抜けてもそのハンドルを保持しとく必要がある場合に使うものであり,必要がなくなったら自分で Dispose()
する必要があります。基本的に使う必要はありません。V8 のほとんどの API(*::New()
とか)の戻り値は Local<T>
ベースですし。
Acme::JavaScript::V8
の場合だと,
つまりほとんど Local<T>
です。
mod_v8pp の場合だと,リクエストごとにコンテキストを生成,ソースコンパイル,実行,を行っているので Local<T>
しか使っていません。
そのたあれこれ
- そんな便利な
Handle<T>
系テンプレートですが,自作クラスをその対象とすることはほぼ無理- プリミティブは V8 library が自力でヒープから取得して初期化したりしてる
- それで GC の対象にしてる
- コンパイル,というのは,字句的なコンパイルだけやってるわけではないっぽい
後者はどういうことか,というと,
var a = 1; var b = a + c; /* 'c' is not defined (compile time error) */ foo(); /* 'foo' is not defined (compile time error) */
これらがコンパイル時にエラーになります。同一コンテキストで,
var d = a + 5;
をコンパイルする場合はエラーにはなりません(a
は同一コンテキストで先ほど定義されたから)。
関数や変数が定義されているかどうかを実行フェーズで判断するインタプリタ処理系が多いんですけど(そのほうが実行時のダイナミズムを確保しやすいから),ちょっと意外でした。
実使用上は,コンテキスト生成時に指定するグローバル ObjectTemplate
の NamedPropertyGetter
等を利用すると,なんとかなるのかなぁ。
Object
に内部的な値を格納する方法
v8::Objectに内部保持するnativeな値(例えばハンドルとか)を保持するには
FILE* fp = fopen(...); v8::Local<v8::Object> obj = v8::Object::New(); obj->Set(v8::String::New("value"), v8::External::New((void*)fp));という様に、v8::Externalでメンバを追加してやれば良い。
Big Sky :: javascript v8エンジンでv8::Objectに内部的な値を格納する方法が分かった。
もちろんそれで動くんですけど,外にみせたくない内部値を扱うには,ObjectTemplate
の InternalField を利用するほうが外にさらされないので better だと思います。XS の MAGIC EXT みたいな感じです。
ファイルハンドルを操るばあいの実例。ほんとは close の際に InternalField[0] を NULL にしたり,妥当性をチェックするべきなんですが,とりあえず。
#include <stdio.h> #include <v8.h> using namespace v8; static Handle<Value> _p(const Arguments& args) { HandleScope scope; String::AsciiValue str(args[0]); fputs(*str, stdout); return Undefined(); } static Handle<Value> _open(const Arguments& args) { HandleScope scope; if (args.Length () != 2) return ThrowException(String::New("usage: open(fname, mode)")); String::AsciiValue fname(args[0]); String::AsciiValue mode(args[1]); FILE *fh = fopen(*fname, *mode); if (! fh) return ThrowException(String::New("cannot open file")); args.This()->SetInternalField(0, External::New(fh)); return args.This(); } static Handle<Value> _close(const Arguments& args) { HandleScope scope; Local<Value> intl_field = args.This()->GetInternalField(0); FILE *fh = reinterpret_cast<FILE *>(Handle<External>::Cast(intl_field)->Value()); fclose(fh); return True(); } static Handle<Value> _gets(const Arguments& args) { HandleScope scope; static char buffer[1024]; Local<Value> intl_field = args.This()->GetInternalField(0); FILE *fh = reinterpret_cast<FILE *>(Handle<External>::Cast(intl_field)->Value()); if (! fgets(buffer, 1024, fh)) { return Undefined(); } return String::New(buffer); } static Handle<FunctionTemplate> create_file_object_template(void) { HandleScope scope; Handle<FunctionTemplate> ft = FunctionTemplate::New(); Handle<ObjectTemplate> ot = ft->InstanceTemplate(); ot->Set(String::New("open"), FunctionTemplate::New(_open)); ot->Set(String::New("close"), FunctionTemplate::New(_close)); ot->Set(String::New("gets"), FunctionTemplate::New(_gets)); ot->SetInternalFieldCount(1); return ft; } int main(int argc, char *argv[]) { HandleScope scope; TryCatch try_catch; Handle<ObjectTemplate> global = ObjectTemplate::New(); // debugging purpose global->Set(String::New("p"), FunctionTemplate::New(_p)); global->Set( String::New("File"), create_file_object_template(), PropertyAttribute(ReadOnly | DontDelete) ); Handle<Context> context = Context::New(NULL, global); Context::Scope context_scope(context); Handle<String> source = String::New( "var f = new File();\n" "\n" "f.open(\"test.txt\", \"r\");\n" "var s;\n" "while (s = f.gets()) {\n" " p(s);\n" "}\n" "f.close();\n" ); Handle<Script> compiled = Script::Compile(source, Undefined()); if (compiled.IsEmpty()) { String::AsciiValue error(try_catch.Exception()); fprintf(stderr, "compile error: %s\n", *error); } else { Handle<Value> result = compiled->Run(); if (result.IsEmpty()) { String::AsciiValue error(try_catch.Exception()); fprintf(stderr, "execute error: %s\n", *error); } else { } } return 0; }
destruction 時に自動的に close()
するようにしたかったんですが,やりかたがわかりませんでした((Persistent<Object>::MakeWeak()
するのもなんか違うような気が……。SetGlobalGCPrologueCallback()
でなんとかするのかなぁ。それも違うよなぁ。))。とりあえず明示的に close()
してください。
いったん FunctionTemplate
を作ってそこの InstanceTemplate
を利用している理由は,JavaScript 側で var f = new File();
みたいに関数オブジェクトを利用した new
を利用させる(ほうが見目麗しい)からです。
new
する必要がない場合,たとえば,すでに存在する外部リソースを JavaScript で扱う場合は,直接 ObjectTemplate::New()
すればいいです(もちろん FunctionTemplate
経由でもいいんですけど)。
サンプルとして Apache の apr_table_t *
型の(ハッシュ)テーブルを V8 JavaScript で扱う例をあげます。ちまちま JavaScript Array を構築してもいいんですけど,ObjectTemplate
を利用するとプロパティアクセサを自分で指定できるので,いい感じです。
static Handle<Value> apr_table_property_getter(Local<String> property, const AccessorInfo& info) { Handle<Value> intl_field = info.This()->GetInternalField(0); apr_table_t *table = (apr_table_t *) Handle<External>::Cast(intl_field)->Value(); String::AsciiValue key(property); const char *value = apr_table_get(table, *key); if (value) return String::New(value); else return Undefined(); } /******* snip snip snip *******/ static Handle<ObjectTemplate> create_apr_table_ro_template(void) { Handle<ObjectTemplate> ot = ObjectTemplate::New(); ot->SetNamedPropertyHandler(apr_table_property_getter, NULL, apr_table_property_query, NULL, apr_table_property_enumerator); ot->SetInternalFieldCount(1); return ot; } static Handle<Object> create_apr_table(Handle<ObjectTemplate>ot, apr_table_t *table) { Handle<Object> object = ot->NewInstance(); object->SetInternalField(0, External::New(table)); return object; } /******* snip snip snip *******/ void sample(request_rec *r); { apr_table_t *table = r->headers_in; Handle<ObjectTemplate> apr_table_ro_template = create_apr_table_ro_template(); Handle<Value> headers_in = create_apr_table(apr_table_ro_template, table); /* これで headers_in を JavaScript で扱えるようになりました */ }
本筋とは関係ないですが,SetNamedPropertyHandler()
するときに任意(といっても Handle<Value>
型)の Data
をわたせます。が,あくまで対象は ObjectTemplate
です。だから,あんまり用途はありません(いくつかの違う種類の class を同じ callback で利用する場合くらい)。各インスタンスごとの秘密データは,さきにあげたように InternalField
経由で設定しておくべき。