JSAPI Cookbook

この記事は僅かなJavaScript共通の用語であるJSAPIについて記載しています。

Note: The FOSS wiki page contains a few links to other libraries and programs that can make life easier when using SpiderMonkey and 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;

エラーメッセージを他国間対応にするには、 SyntaxErrorTypeError等のようにエラー種別を投げるように、 JS_ReportErrorNumber を代わりに用います。

JavaScriptでは Error オブジェクトだけでなく、他の値を渡す事もサポートしています。 C/C++ から jsval という値を受け取るためにJS_SetPendingException を使います。

// JavaScript
throw exc;
/* JSAPI */
JS_SetPendingException(cx, exc);
return JS_FALSE;

JS_ReportError が新規に Error オブジェクトを生成するとき、現在実行中のスタックの先頭にあるJavaScriptコードの行の属性である fileNamelineNumber をセットします。 これは通常、あなたが本当に必要としているものとは一致しないネイティブ関数を呼び出したコードの行になります。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 を使い、 yJSObject *の型にキャストします。

/* 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_ENUMERATEenumerable: true,に一致し、 JSPROP_PERMANENTconfigurable: 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バージョンでは、 GetPropFuncSetPropFuncJSNative型の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 and for each.
  • Actually outputting errors.

ドキュメントのタグと貢献者

 このページの貢献者: yoshitanaka
 最終更新者: yoshitanaka,