この記事は僅かなJavaScript共通の用語であるJSAPIについて記載しています。
基礎
Finding the global object
それらのレシピの多くは、最初に現在の大域オブジェクトを見つけることを要求します。
// JavaScript var global = this;
There is a function, JS_GetGlobalForScopeChain(cx)
という関数があります。最良の考え方でありときにそれは大域オブジェクトの取得する場合最良の方法です。しかしJSNative
の中では、これを実行する正しい方法は:
/* JSAPI */ JSBool myNative(JSContext *cx, uintN argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); JSObject *global = JS_GetGlobalForObject(cx, &args.callee()); ... }
関数定義
// JavaScript function justForFun() { return null; }
/* JSAPI */ JSBool justForFun(JSContext *cx, uintN argc, jsval *vp) { JS_SET_RVAL(cx, vp, JSVAL_NULL); return JS_TRUE; } ... /* * Add this to your JSContext setup code. * This makes your C function visible as a global function in JavaScript. */ if (!JS_DefineFunction(cx, global, "justForFun", &justForFun, 0, 0)) return JS_FALSE;
JSAPI関数を一度に定義する場合、JS_DefineFunctions. を用います。 JS_DefineFunctions
.
配列の生成
// JavaScript var x = []; // or "x = Array()", or "x = new Array"
/* JSAPI */ JSObject *x = JS_NewArrayObject(cx, 0, NULL); if (x == NULL) return JS_FALSE;
オブジェクトの生成
// JavaScript var x = {}; // or "x = Object()", or "x = new Object"
/* JSAPI */ JSObject *x = JS_NewObject(cx, NULL, NULL, NULL); if (x == NULL) return JS_FALSE;
オブジェクトの生成と初期化
// JavaScript var person = new Person("Dave", 24);
JavaScriptではとても単純に処理されています。しかし、JSAPIのアプリケーションでは以下に記述するように3つのことを処理する必要があります:
- コンストラクタを参照する、
Person
- 引数を準備する
("Dave", 24)
-
new
キーワードで示す処理を擬似的に作り出すためにJS_New
を呼び出します。
(コンストラクタがどのよいうな引数も取らないならば、2番目の段階は省略して 3段階目の処理である JS_New(cx, constructor, 0, NULL)
を呼び出せます。)
/* JSAPI */ jsval constructor_val; JSObject *constructor; /* BUG - not rooted */ JSString *name_str; jsval argv[2]; /* BUG - not rooted */ JSObject *obj; /* Step 1 - Get the value of |Person| and check that it is an object. */ if (!JS_GetProperty(cx, JS_GetGlobalObject(cx), "Person", &constructor_val)) return JS_FALSE; if (JSVAL_IS_PRIMITIVE(constructor_val)) { JS_ReportError(cx, "Person is not a constructor"); return JS_FALSE; } constructor = JSVAL_TO_OBJECT(constructor_val); /* Step 2 - Set up the arguments. */ name_str = JS_NewStringCopyZ(cx, "Dave"); if (!name_str) return JS_FALSE; argv[0] = STRING_TO_JSVAL(name_str); argv[1] = INT_TO_JSVAL(24); /* Step 3 - Call |new Person(...argv)|, passing the arguments. */ obj = JS_New(cx, constructor, 2, argv); if (!obj) return JS_FALSE;
大域的なJS関数の呼び出し
// JavaScript var r = foo(); // where f is a global function
/* JSAPI * * Suppose the script defines a global JavaScript * function foo() and we want to call it from C. */ jsval r; if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "foo", 0, NULL, &r)) return JS_FALSE;
局所変数を投資てJS関数を呼び出す
// JavaScript var r = f(); // where f is a local variable
/* JSAPI * * Suppose f is a local C variable of type jsval. */ jsval r; if (!JS_CallFunctionValue(cx, NULL, f, 0, NULL, &r) return JS_FALSE;
整数の戻り値
// JavaScript return 23;
/* JSAPI * * Warning: This only works for integers that fit in 32 bits. * Otherwise, convert the number to floating point (see the next example). */ JS_SET_RVAL(cx, vp, INT_TO_JSVAL(23)); return JS_TRUE;
浮動小数点少数の戻り値
// JavaScript return 3.14159;
/* JSAPI */ jsdouble n = 3.14159; return JS_NewNumberValue(cx, n, rval);
例外処理
throw
新規に最も共通の用語である エラー
オブジェクトを生成した場合、それをハンドラに投げる場合 JS_ReportError
が処理を行います。 Note JavaScript の例外はC++の例外処理とは異なります。JSAPIコードでは呼び出し側に誤りを通知するために JS_FALSE
を返す必要があります。
// JavaScript throw new Error("Failed to grow " + varietal + ": too many greenflies.");
/* JSAPI */ JS_ReportError(cx, "Failed to grow %s: too many greenflies.", varietal); return JS_FALSE;
エラーメッセージを他国間対応にするには、 SyntaxError
や TypeError
等のようにエラー種別を投げるように、 JS_ReportErrorNumber
を代わりに用います。
JavaScriptでは Error
オブジェクトだけでなく、他の値を渡す事もサポートしています。 C/C++ から jsval
という値を受け取るためにJS_SetPendingException
を使います。
// JavaScript throw exc;
/* JSAPI */ JS_SetPendingException(cx, exc); return JS_FALSE;
JS_ReportError
が新規に Error
オブジェクトを生成するとき、現在実行中のスタックの先頭にあるJavaScriptコードの行の属性である fileName
と lineNumber
をセットします。 これは通常、あなたが本当に必要としているものとは一致しないネイティブ関数を呼び出したコードの行になります。JSAPIコードは直接 Error
オブジェクトを生成し、コンストラクタに追加の引数を渡す事でことでこれを無視することができます:
// JavaScript throw new Error(message, filename, lineno);
/* JSAPI */ JSBool ThrowError(JSContext *cx, JSObject *global, const char *message, const char *filename, int32 lineno) { JSString *messageStr; JSString *filenameStr; jsval args[3]; jsval exc; messageStr = JS_NewStringCopyZ(cx, message); if (!messageStr) return JS_FALSE; filenameStr = JS_NewStringCopyZ(cx, filename); if (!filenameStr) return JS_FALSE; args[0] = STRING_TO_JSVAL(messageStr); args[1] = STRING_TO_JSVAL(filenameStr); args[2] = INT_TO_JSVAL(lineno); if (JS_CallFunctionName(cx, global, "Error", 3, args, &exc)) JS_SetPendingException(cx, exc); return JS_FALSE; } ... return ThrowError(cx, global, message, __FILE__, __LINE__);
ここのJSAPIコードは、new
がJSAPIを用いて擬似的に作り出すことが難しいため、 new
なしに throw Error(message)
を擬似的に作り出します。この場合、スクリプトが Error
を再定義しなければ、同じ事になります。
catch
// JavaScript try { // try some stuff here; for example: foo(); bar(); } catch (exc) { // do error-handling stuff here }
/* JSAPI */ jsval exc; /* try some stuff here; for example: */ if (!JS_CallFunctionName(cx, global, "foo", 0, NULL, &r)) goto catch_block; /* instead of returning JS_FALSE */ if (!JS_CallFunctionName(cx, global, "bar", 0, NULL, &r)) goto catch_block; /* instead of returning JS_FALSE */ return JS_TRUE; catch_block: if (!JS_GetPendingException(cx, &exc)) return JS_FALSE; JS_ClearPendingException(cx); /* do error-handling stuff here */ return JS_TRUE;
finally
// JavaScript try { foo(); bar(); } finally { cleanup(); }
C/C++の浄化コードがJSAPI内にコールバックしなければ、処理は単純になります:
/* JSAPI */ JSBool success = JS_FALSE; if (!JS_CallFunctionName(cx, global, "foo", 0, NULL, &r)) goto finally_block; /* instead of returning JS_FALSE immediately */ if (!JS_CallFunctionName(cx, global, "bar", 0, NULL, &r)) goto finally_block; success = JS_TRUE; /* Intentionally fall through to the finally block. */ finally_block: cleanup(); return success;
しかしながら、 cleanup()
が実際にJavaScriptの関数であれば、それらはcatchされます。エラーが発生したとき、 JSContext
の例外処理の待機がセットされます。 これは上の例で foo()
や bar()
で発生するならば、待機中の例外処理は より悪い状態になるであろう cleanup()
処理を呼び出しているときにセットされます。これを避ける為に、JSAPIコードの実装では finally
ブロックには以下のことが必要ですt:
- 古い例外を保存する
- cleanupコードを走らせる為に、待機中の例外処理をクリアします
- あなたのcleanup処理wp実行します
- 古い例外を復帰させます
- 例外が発生していたならば、例外処理を上位に伝えるために、JS_FALSEを戻り値として返します
/* JSAPI */ JSBool success = JS_FALSE; JSExceptionState *exc_state; if (!JS_CallFunctionName(cx, global, "foo", 0, NULL, &r)) goto finally_block; /* instead of returning JS_FALSE immediately */ if (!JS_CallFunctionName(cx, global, "bar", 0, NULL, &r)) goto finally_block; success = JS_TRUE; /* Intentionally fall through to the finally block. */ finally_block: exc_state = JS_SaveExceptionState(cx); if (exc_state == NULL) return JS_FALSE; JS_ClearPendingException(cx); if (!JS_CallFunctionName(cx, global, "cleanup", 0, NULL, &r)) { /* The new error replaces the previous one, so discard the saved exception state. */ JS_DropExceptionState(cx, exc_state); return JS_FALSE; } JS_RestoreExceptionState(cx, exc_state); return success;
オブジェクトの属性
属性の取得
// JavaScript var x = y.myprop;
JSAPI関数で、属性を取得する関数が JS_GetProperty
です。JSObject *
を引数として必要とします。 JavaScriptの値は、通常 jsval
変数に保存されるため、変数のキャストまたは変換が必要になります。
y
(ブール値、数値、文字列、 null
, または undefined
などではなく)がオブジェクトであることが確実な場合、これは非常に単純です。 JSVAL_TO_OBJECT
を使い、 y
を JSObject *
の型にキャストします。
/* JSAPI */ jsval x; assert(!JSVAL_IS_PRIMITIVE(y)); if (!JS_GetProperty(cx, JSVAL_TO_OBJECT(y), "myprop", &x)) return JS_FALSE;
もし y
がオブジェクトでなければ、コードは壊れます。これはときに受け入れ難いことです。代替的にJavaScriptに振る舞いを擬似的に実行することになります。非常に良い考えですJavaScriptはクラッシュしません。しかし、その正確な振る舞いの実装では、非常に複雑なものになります。
多くの実装では、特別に処理が扱いやすくなるわけではありません。通常、 !JSVAL_IS_PRIMITIVE(y)
を検査し、良いメッセージとともに Error
を投げることが最良な処理です。
/* JSAPI */ jsval x; if (JSVAL_IS_PRIMITIVE(y)) return ThrowError(cx, global, "Parameter y must be an object.", __FILE__, __LINE__); /* see the #throw example */ if (!JS_GetProperty(cx, JSVAL_TO_OBJECT(y), "myprop", &x)) return JS_FALSE;
属性の設定
// JavaScript y.myprop = x;
y
がオブジェクトではない場合を懸念するならば、上述の属性の取得を参照して下さい。
/* JSAPI */ assert(!JSVAL_IS_PRIMITIVE(y)); if (!JS_SetProperty(cx, JSVAL_TO_OBJECT(y), "myprop", &x)) return JS_FALSE;
属性の検査
// JavaScript if ("myprop" in y) { // then do something }
y
がオブジェクトではない場合を懸念するならば、上述の属性の取得を参照して下さい。
/* JSAPI */ JSBool found; assert(!JSVAL_IS_PRIMITIVE(y)); if (!JS_HasProperty(cx, JSVAL_TO_OBJECT(y), "myprop", &found)) return JS_FALSE; if (found) { // then do something }
固定属性を定義する
これはビルトイン関数 Object.defineProperty()
に関わる最初の3つの例です。この関数は、オブジェクトの個別の属性の挙動に関してはっきりした操作をJavaScriptのコードに与えます。
この関数を使って、上書きや削除できない固定属性を生成できます。writable: false
によって属性を読み出し専用に作り、 configurable: false
に設定して、再定義や削除されることから防ぎます。 フラグ enumerable: true
は forループ内にあるときにこの属性が設定されます。
// JavaScript Object.defineProperty(obj, "prop", {value: 123, writable: false, enumerable: true, configurable: false});
JSAPI関数の類似した関数に、 JS_DefineProperty
があります。属性 JSPROP_READONLY
を持っており、その属性はwriteable: false
に一致します。JSPROP_ENUMERATE
は enumerable: true
,に一致し、 JSPROP_PERMANENT
は configurable: false
にその属性が一致します。これらの設定の相対的な挙動を得るためには、必要ない属性のビットを単純に省いて下さい。
/* JSAPI */ if (!JS_DefineProperty(cx, obj, "prop", INT_TO_JSVAL(123), JS_PropertyStub, JS_StrictPropertyStub, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) { return JS_FALSE; }
設定と取得の属性の定義
Object.defineProperty()
は二つのアクセス関数の属性を定義する為に用いることができます。
// JavaScript Object.defineProperty(obj, "prop", {get: GetPropFunc, set: SetPropFunc, enumerable: true});
JSAPIバージョンでは、 GetPropFunc
と SetPropFunc
が JSNative
型のC/C++ 関数関数として用意されています。of type .
/* JSAPI */ if (!JS_DefineProperty(cx, obj, "prop", JSVAL_VOID, (JSPropertyOp) GetPropFunc, (JSStrictPropertyOp) SetPropFunc, JSPROP_SHARED | JSPROP_NATIVE_ACCESSORS | JSPROP_ENUMERATE)) { return JS_FALSE; }
取得のための呼び出し専用属性の定義
// JavaScript Object.defineProperty(obj, "prop", {get: GetPropFunc, enumerable: true});
JSAPIバージョン では、属性を読み出し専用に定義し、設定用にはNULLを通します。
/* JSAPI */ if (!JS_DefineProperty(cx, obj, "prop", JSVAL_VOID, GetPropFunc, NULL, JSPROP_SHARED | JSPROP_NATIVE_ACCESSORS | JSPROP_ENUMERATE)) { return JS_FALSE; }
プロトタイプチェインの働き
String.prototypeにネイティブでは読み出し専用属性を定義します
// JavaScript Object.defineProperty(String.prototype, "md5sum", {get: GetMD5Func, enumerable: true});
何かが大域的なStringオブジェクトを別な何かに取り替えるならば、以下のようなトリックは動作しません。
/* JSAPI */ JSObject *string, *string_prototype; jsval val; // Get the String constructor from the global object. if (!JS_GetProperty(cx, global, "String", &val)) return JS_FALSE; if (JSVAL_IS_PRIMITIVE(val)) return ThrowError(cx, global, "String is not an object", __FILE__, __LINE__); string = JSVAL_TO_OBJECT(val); // Get String.prototype. if (!JS_GetProperty(cx, string, "prototype", &val)) return JS_FALSE; if (JSVAL_IS_PRIMITIVE(val)) return ThrowError(cx, global, "String.prototype is not an object", __FILE__, __LINE__); string_prototype = JSVAL_TO_OBJECT(val); // ...and now we can add some new functionality to all strings. if (!JS_DefineProperty(cx, string_prototype, "md5sum", JSVAL_VOID, GetMD5Func, NULL, JSPROP_SHARED | JSPROP_NATIVE_ACCESSORS | JSPROP_ENUMERATE)) return JS_FALSE;
Wanted
- Simulating
for
andfor each
. - Actually outputting errors.