JavaScript エンジンを埋め込む方法

特により良いコードの例として JSAPI User Guide も参照して下さい。

チュートリアル要点

Hello World サンプル組み込みアプリケーション

次のコードは、SpiderMonkey を埋め込んで単純な JavaScript スクリプトを実行する方法を示す非常に単純なアプリケーションです。下のコードのサンプルをビルドして実行するための手順を参照してください。

コードは SpiderMonkey のバージョンごとに異なりますので、SpiderMonkey の正しいバージョンを選択してください。

SpiderMonkey 24

// following code might be needed in some case
// #define __STDC_LIMIT_MACROS
// #include <stdint.h>
#include "jsapi.h"

/* The class of the global object. */
static JSClass global_class = {
    "global",
    JSCLASS_GLOBAL_FLAGS,
    JS_PropertyStub,
    JS_DeletePropertyStub,
    JS_PropertyStub,
    JS_StrictPropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
};

int main(int argc, const char *argv[])
{
    JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024, JS_USE_HELPER_THREADS);
    if (!rt)
        return 1;

    JSContext *cx = JS_NewContext(rt, 8192);
    if (!cx)
        return 1;

    { // Scope for our various stack objects (JSAutoRequest, RootedObject), so they all go
      // out of scope before we JS_DestroyContext.

      JSAutoRequest ar(cx); // In practice, you would want to exit this any
                            // time you're spinning the event loop

      JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr));
      if (!global)
          return 1;

      JS::RootedValue rval(cx);

      { // Scope for JSAutoCompartment
        JSAutoCompartment ac(cx, global);
        JS_InitStandardClasses(cx, global);

        const char *script = "'hello'+'world, it is '+new Date()";
        const char *filename = "noname";
        int lineno = 1;
        bool ok = JS_EvaluateScript(cx, global, script, strlen(script), filename, lineno, rval.address());
        if (!ok)
          return 1;
      }

      JSString *str = rval.toString();
      printf("%s\n", JS_EncodeString(cx, str));
    }

    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);
    JS_ShutDown();
    return 0;
}

SpiderMonkey 31

// following code might be needed in some case
// #define __STDC_LIMIT_MACROS
// #include <stdint.h>
#include "jsapi.h"

/* The class of the global object. */
static JSClass global_class = {
    "global",
    JSCLASS_GLOBAL_FLAGS,
    JS_PropertyStub,
    JS_DeletePropertyStub,
    JS_PropertyStub,
    JS_StrictPropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    JS_GlobalObjectTraceHook
};

int main(int argc, const char *argv[])
{
    JS_Init();

    JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024, JS_USE_HELPER_THREADS);
    if (!rt)
        return 1;

    JSContext *cx = JS_NewContext(rt, 8192);
    if (!cx)
        return 1;

    { // Scope for our various stack objects (JSAutoRequest, RootedObject), so they all go
      // out of scope before we JS_DestroyContext.

      JSAutoRequest ar(cx); // In practice, you would want to exit this any
                            // time you're spinning the event loop

      JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, JS::FireOnNewGlobalHook));
      if (!global)
          return 1;

      JS::RootedValue rval(cx);

      { // Scope for JSAutoCompartment
        JSAutoCompartment ac(cx, global);
        JS_InitStandardClasses(cx, global);

        const char *script = "'hello'+'world, it is '+new Date()";
        const char *filename = "noname";
        int lineno = 1;
        bool ok = JS_EvaluateScript(cx, global, script, strlen(script), filename, lineno, &rval);
        if (!ok)
          return 1;
      }

      JSString *str = rval.toString();
      printf("%s\n", JS_EncodeString(cx, str));
    }

    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);
    JS_ShutDown();
    return 0;
}

SpiderMonkey 38

// following code might be needed in some case
// #define __STDC_LIMIT_MACROS
// #include <stdint.h>
#include "jsapi.h"

/* The class of the global object. */
static JSClass global_class = {
    "global",
    JSCLASS_GLOBAL_FLAGS,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    JS_GlobalObjectTraceHook
};

int main(int argc, const char *argv[])
{
    JS_Init();

    JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024);
    if (!rt)
        return 1;

    JSContext *cx = JS_NewContext(rt, 8192);
    if (!cx)
        return 1;

    { // Scope for our various stack objects (JSAutoRequest, RootedObject), so they all go
      // out of scope before we JS_DestroyContext.

      JSAutoRequest ar(cx); // In practice, you would want to exit this any
                            // time you're spinning the event loop

      JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, JS::FireOnNewGlobalHook));
      if (!global)
          return 1;

      JS::RootedValue rval(cx);

      { // Scope for JSAutoCompartment
        JSAutoCompartment ac(cx, global);
        JS_InitStandardClasses(cx, global);

        const char *script = "'hello'+'world, it is '+new Date()";
        const char *filename = "noname";
        int lineno = 1;
        JS::CompileOptions opts(cx);
        opts.setFileAndLine(filename, lineno);
        bool ok = JS::Evaluate(cx, global, opts, script, strlen(script), &rval);
        if (!ok)
          return 1;
      }

      JSString *str = rval.toString();
      printf("%s\n", JS_EncodeString(cx, str));
    }

    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);
    JS_ShutDown();
    return 0;
}

SpiderMonkey 45

// following code might be needed in some case
// #define __STDC_LIMIT_MACROS
// #include <stdint.h>
#include "jsapi.h"
#include "js/Initialization.h"

/* The class of the global object. */
static JSClass global_class = {
    "global",
    JSCLASS_GLOBAL_FLAGS,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    JS_GlobalObjectTraceHook
};

int main(int argc, const char *argv[])
{
    JS_Init();

    JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024);
    if (!rt)
        return 1;

    JSContext *cx = JS_NewContext(rt, 8192);
    if (!cx)
        return 1;

    { // Scope for our various stack objects (JSAutoRequest, RootedObject), so they all go
      // out of scope before we JS_DestroyContext.

      JSAutoRequest ar(cx); // In practice, you would want to exit this any
                            // time you're spinning the event loop

      JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, JS::FireOnNewGlobalHook));
      if (!global)
          return 1;

      JS::RootedValue rval(cx);

      { // Scope for JSAutoCompartment
        JSAutoCompartment ac(cx, global);
        JS_InitStandardClasses(cx, global);

        const char *script = "'hello'+'world, it is '+new Date()";
        const char *filename = "noname";
        int lineno = 1;
        JS::CompileOptions opts(cx);
        opts.setFileAndLine(filename, lineno);
        bool ok = JS::Evaluate(cx, opts, script, strlen(script), &rval);
        if (!ok)
          return 1;
      }

      JSString *str = rval.toString();
      printf("%s\n", JS_EncodeString(cx, str));
    }

    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);
    JS_ShutDown();
    return 0;
}

SpiderMonkey 52

#include "jsapi.h"
#include "js/Initialization.h"

static JSClassOps global_ops = {
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    JS_GlobalObjectTraceHook
};

/* The class of the global object. */
static JSClass global_class = {
    "global",
    JSCLASS_GLOBAL_FLAGS,
    &global_ops
};

int main(int argc, const char *argv[])
{
    JS_Init();

    JSContext *cx = JS_NewContext(8L * 1024 * 1024);
    if (!cx)
        return 1;
    if (!JS::InitSelfHostedCode(cx))
        return 1;

    { // Scope for our various stack objects (JSAutoRequest, RootedObject), so they all go
      // out of scope before we JS_DestroyContext.

      JSAutoRequest ar(cx); // In practice, you would want to exit this any
                            // time you're spinning the event loop

      JS::CompartmentOptions options;
      JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, JS::FireOnNewGlobalHook, options));
      if (!global)
          return 1;

      JS::RootedValue rval(cx);

      { // Scope for JSAutoCompartment
        JSAutoCompartment ac(cx, global);
        JS_InitStandardClasses(cx, global);

        const char *script = "'hello'+'world, it is '+new Date()";
        const char *filename = "noname";
        int lineno = 1;
        JS::CompileOptions opts(cx);
        opts.setFileAndLine(filename, lineno);
        bool ok = JS::Evaluate(cx, opts, script, strlen(script), &rval);
        if (!ok)
          return 1;
      }

      JSString *str = rval.toString();
      printf("%s\n", JS_EncodeString(cx, str));
    }

    JS_DestroyContext(cx);
    JS_ShutDown();
    return 0;
}

Hello Worldの例のビルドと実行

ビルドコマンドラインは OS とツールに依存します。Mac と Linux のコマンドラインのサンプルを次に示します (<objdir> は SpiderMonkey がビルドされたディレクトリです)。

# SpiderMonkey のデバッグビルドを使用している場合は、以下のコマンドに加えて -DDEBUG が必要です。
# SpiderMonkey 31 以外のバージョンを使用している場合は、-lmozjs-XX を自分のバージョンに変更してください。

[Mac]
clang++ -std=c++11 -I<objdir>/dist/include -L<objdir>/dist/lib helloworld.cpp -o helloworld  -lmozjs-31 -lz
[Linux]
g++ -std=c++11 -I<objdir>/dist/include -L<objdir>/dist/lib helloworld.cpp -o helloworld  -lmozjs-31 -lz -lpthread -ldl

"helloworld, it is TIME" と表示されます (ここでは TIME が現在の時刻です)。

  1. Make sure the build computer has the prerequisites for building SpiderMonkey: Linux, Windows, Mac OS X, others. For Windows, the following steps will assume that you have installed the MozillaBuild package.
  2. Get the SpiderMonkey source code. You can download a source archive or use Mercurial (hg) to pull the SpiderMonkey repository. On Windows, do not install the SpiderMonkey source code under the MSYS root directory (which is usually c:\mozilla-build\msys). Instead use something like c:\mozjs-31.2.0
  3. Compile SpiderMonkey using the build instructions at SpiderMonkey Build Documentation. By default this will build a SpiderMonkey shared library that you will link into your application in a later step.
  4. Copy the code example above into a text editor and save the file as helloworld.cpp in the SpiderMonkey js\src directory. To get a copy of the code sample without line numbers, hover over the sample near the top until buttons appear. Then click the view source button, and copy the code from the window that appears.
  5. Compile the helloworld application and link to the SpiderMonkey library.
  6. Run the helloworld executable at the command line:
    ./helloworld

JavaScript から C 関数の呼び出し方

doit という名前のC関数について言及します。これは呼び出されるときに少なくとも二つの実際のパラメータを使います。(もし呼びだし元がより少ないパラメータを使う場合、JSエンジンは無くなった一つの変数に対して未定義の変数が渡される事を確認すべきです):

#define DOIT_MINARGS 2

static JSBool
doit(JSContext *cx, unsigned argc, jsval *vp)
{
    jsval *argv = JS_ARGV(cx, vp);
    /*
     * Look in argv for argc actual parameters, set *rval to return a
     * value to the caller.
     */
    ...
}

そして、以下のようにコーディングし、JSに渡します:

ok = JS_DefineFunction(cx, global, "doit", doit, DOIT_MINARGS, 0);

または、ネイティブな関数群として定義するならば、おそらくそれらをテーブルの中に置き、関数テーブルとして定義するでしょう:

static JSFunctionSpec my_functions[] = {
    {"doit", doit, DOIT_MINARGS, 0, 0},
    etc...
    {0,0,0,0,0},
};

(最終的に、すべてのNULL 関数はテーブルを終端します)そして、以下のようにします:

ok = JS_DefineFunctions(cx, global, my_functions);

C から JavaScript の関数の呼び出し方

クリックイベントを最上位の UI から座標 (x,y) の UI 要素に焦点をあてます:

JSObject *target, *event;
jsval argv[1], rval;

/*
 * Find event target and make event object to represent this click.
 * Pass cx to NewEventObject so JS_NewObject can be called.
 */
target = FindEventTargetAt(cx, global, x, y);
event = NewEventObject(cx, "click", x, y);
argv[0] = OBJECT_TO_JSVAL(event);

/* To emulate the DOM, you might want to try "onclick" too. */
ok = JS_CallFunctionName(cx, target, "onClick", 1, argv, &rval);

/* Now test rval to see whether we should cancel the event. */
if (JSVAL_IS_BOOLEAN(rval) && !JSVAL_TO_BOOLEAN(rval))
    CancelEvent(event);

繰り返しますが、ここではエラーチェックは無視しています。(関数呼び出し後の 戻り値 !ok のテストような)、いくつかのCのイベント管理処理やハンドラーが偽値を返したときのイベントをキャンセルする場合のDOMの伝統的なやり方を適切にエミュレートするために疑似コードを用いています。

Original Document Information

  • Author: Brendan Eich
  • Last Updated Date: 21 February, 2000