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でメンバを追加してやれば良い。ただ分からなくなってた原因としては、shell.ccが持ってるPrintという関数が
v8::Handle<v8::Value> Print(const v8::Arguments& args) {
bool first = true;
for (int i = 0; i < args.Length(); i++) {
v8::HandleScope handle_scope;
if (first) {
first = false;
} else {
printf(" ");
}
v8::String::AsciiValue str(args[i]);
printf("%s", *str);
}
printf("\n");
return v8::Undefined();
}
となってて、何でも文字列化してくれる様に思えてた。で実際
var obj = test();
print(obj.value); // ここでクラッシュ
こんな事やると落ちてしまうんだけど、良く考えたらExternalって言ってるんだから値の出しようもない。確かに以下の様なコードをshell.ccに含ませ
global->Set(v8::String::New("hoge"), v8::External::New(NULL));
javascriptから"print(hoge)"で例外を発生させた際のデバッグ出力で確かめたら
==== Stack trace ============================================
(No security context)
1: DefaultString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#)
(No security context)
2: ToString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#)
6: arguments adaptor frame: 1->0
Security context: 00E006A1 <FixedArray[41]>#2#
7: /* anonymous */(this=00C10361 <JS Global Object>#3#)
==== Details ================================================
(No security context)
[1]: DefaultString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#) {
// stack-allocated locals
var v = 01000135 <undefined>
var s = 01000135 <undefined>
// expression stack (top to bottom)
[04] : 01000361 <String[8]: toString>
[03] : 00E0068D <Proxy>#1#
[02] : 00E0068D <Proxy>#1#
--------- s o u r c e c o d e ---------
function DefaultString(x) {? if ((typeof(x.toString) === 'function')) {? var s = x.toString();? if (%IsPrimitive(s)) return s;? }? if ((typeof(x.valueOf) === 'function')) {? var v = x.valueOf();? if (%IsPrimitive(v)) return v;? }? throw %MakeTypeError('cannot_convert_to_primitive', []);?}
-----------------------------------------
}
(No security context)
[2]: ToString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#) {
// expression stack (top to bottom)
[02] : 01002845 <String[13]: DefaultString>
[01] : 00C104BD <JS Object>#0#
[00] : 0100232D <String[8]: ToString>
--------- s o u r c e c o d e ---------
function ToString(x) {? if ((typeof(x) === 'string')) return x;? if ((typeof(x) === 'number')) return %NumberToString(x);? if ((typeof(x) === 'boolean')) return x ? 'true' : 'false';? if ((typeof(x) === 'undefined')) return 'undefined';? return ((x === null)) ? 'null' : %ToString(%DefaultString(x));?}
-----------------------------------------
とNULL参照している事が分かる。v8からすると"訳分かんない物をToStringして表示しようとしただけさ。"と言ったところか。つまりはv8::Externalの使い方が悪いわけではなく、v8::Externalを表示しようとしてたPrint関数がマズイのだ。さらに元々Externalなんか扱ってなかったshell.ccにExternalを埋め込んだ犯人が悪い。そう...私。
言ってしまえばExternal組み込んだんだから、Printもちゃんと修正しろよって事ですね。
if (argv[0]->IsExternal()) {
printf("[native code]");
continue;
}
よくブラウザなんかで見かける"[native code]"という表示になる様にしました。この修正は「Google の JavaScript エンジン v8 で FastCGI する - TokuLog 改めB日記」での成果物"fcgi-v8"にも組み込んであります。
ただ、Extenalに保持する様な値というのは、ハンドルとかメモリ確保した領域だとか、状態を保持する様な物のはずなので、実際には確保したポインタ等のリストをグローバルな領域にを持っておき、終了時に解放するかPersistentなObjectTemplateでインスタンスを作り、WeakReferenceCallbackにて解放するのがベターという事になる。
Externalを使う側からすると、そこに何が入っててもおかしい訳でもなく、v8として取り決めもない(引数の型はvoidポインタ)。よって複数の種類を格納したい場合は種類も判別出来る物、例えば
typedef enum _OBJECT_TYPE {
OBJECT_TYPE_FILE_POINTER, // FILE*
OBJECT_TYPE_WINDOW_HANDLE // ウィンドウハンドル
} OBJECT_TYPE;
typedef struct _OBJECT_VALUE {
OBJECT_TYPE type;
void* object;
} OBJECT_VALUE;
// 設定
OBJECT_VALUE* val = new OBJECT_VALUE;
val->type = OBJECT_TYPE_FILE_POINTER;
val->object = fopen("test.dat", "rb");
obj->Set(v8::String::New("value"), v8::External::New((void*)val));
// 取得
v8::Local<v8::Value> valueobj = obj->Get(v8::String::New("value"));
if (!valueobj->IsExternal()) return v8::ThrowException(v8::String::New("Exception: invalid object"));
OBJECT_VALUE* val = static_cast< OBJECT_VALUE* >(v8::Handle<v8::External>::Cast(valueobj)->Value());
if (val->type == OBJECT_TYPE_FILE_POINTER) {
// FILE*な処理
} else
if (val->type == OBJECT_TYPE_WINDOW_HANDLE) {
// ウィンドウハンドルな処理
}
こんな構造と設定および取得をすべき、という事になる。ここまで気付くのに結構時間を使ってしまった。さてこのPrint関数ですが、↑のリンクにある"fcgi-v8"には「Big Sky :: Google Chromeが使っているjavascript v8エンジンにTwitterにアクセス出来るクラスを作ってポストする。」でご紹介したtwitter-v8とは違う方法でPrintを実装してあります。
twitter-v8の方はコンソールに出力する物なのでsetlocaleとワイドキャラクタ出力(%S)でOKですが、fcgi-v8の場合はUCSからUTF-8への変換を組み込んであります。一応v8のプロジェクトサイトでIssue 17 : 「Add Strign::WriteUtf8」としてリクエストされているので、もしかすると今後このコードは必要なくなるかもしれませんね。
v8エンジンは高速な上、今まで私が見た中で一番組み込みやすい代物になっているかと思います。皆さんもv8使った面白いもの作ってみませんか。
twitter-v8、fcgi-v8それぞれ参加者をお待ちしております。