Skip to content

C API

YanmaJS は Zig API に加えて、共有ライブラリ(libyanmajs.so)と C11 ヘッダ(yanmajs_capi/include/yanmajs.h)による C ABI を提供します。C/C++ 製の GUI アプリケーションや、C FFI を持つ他言語からの組み込みに使えます。C ABI 層のソースは yanmajs_capi/ にあり、ルートの build.zigcapi ステップでビルドします。

zig build capi       # zig-out/lib/libyanmajs.so と zig-out/include/yanmajs.h を生成
zig build examples-c # example/c/ の 4 例をビルドして実行

make build-capi / make examples-c でも同様のことができます(開発ガイド参照)。

API 面は QuickJS(quickjs.h)を設計モデルにした「GUI 組み込みに必要な最小完全集合」です。宣言と規約の一次情報はヘッダ yanmajs_capi/include/yanmajs.h のコメント、実装は yanmajs_capi/src/capi.zig です。

基本規約

値とライフタイム

typedef uint64_t YjsValue;

YjsValue は NaN ボクシングされた 64bit のビット列で、コピーは値参照のコピーです。API が返す YjsValue はすべて借用であり、次にアロケーションやスクリプト実行を伴う API を呼ぶまでしか有効性が保証されません(GC に回収されうるため)。プリミティブ(undefined/null/bool/number)はビット列自体が値なのでこの制約を受けません。

呼び出しをまたいでオブジェクト系の値(文字列・オブジェクト・配列・関数など)を保持する場合は、必ず yjs_protect でルート化します。

YjsPersistent p = yjs_protect(ctx, obj);
/* ... 他の API 呼び出し ... */
YjsValue alive = yjs_persistent_get(ctx, p); /* GC 後も生存 */
yjs_unprotect(ctx, p);

GC は non-moving なので、ルート化された値のビット列は変化しません。なお現行版では unprotect したスロットは再利用されません(protect/unprotect を無制限に繰り返すホストのみ注意)。

文字列

  • 入力はすべて UTF-8 の (ポインタ, 長さ) ペアです。NUL 終端は不要で、参照もされません。
  • 出力(yjs_to_string)は NUL 終端された API 所有のバッファです。解放は必ず yjs_string_free で行ってください。free() を直接呼んではいけません(エンジンとホストでアロケータが異なる可能性があるため)。
size_t len;
char *text = yjs_to_string(ctx, v, &len);
printf("%s\n", text);
yjs_string_free(ctx, text);

例外

失敗しうる呼び出しは、実値の代わりにセンチネル値(yjs_exception())を返します。yjs_is_exception() で判定し、yjs_get_exception() で保留中の例外オブジェクトを取得します(取得と同時にクリアされる消費型です)。

YjsValue v = yjs_eval(ctx, src, strlen(src));
if (yjs_is_exception(v)) {
    YjsValue exc = yjs_get_exception(ctx);
    char *msg = yjs_to_string(ctx, exc, NULL); /* Error はスタックトレース付き */
    fprintf(stderr, "%s\n", msg);
    yjs_string_free(ctx, msg);
}

センチネルは予約ビットパターンであり、本物の JS 値として現れることはありません。プロパティやグローバルへ格納したり、呼び出し引数として渡してはいけません。

Hello, world

example/c/hello.c の骨子です。

#include <stdio.h>
#include <string.h>
#include <yanmajs.h>

int main(void) {
    YjsContext *ctx = yjs_new();

    const char *src = "const greet = (who) => `hello, ${who}!`; greet('yanmajs')";
    YjsValue result = yjs_eval(ctx, src, strlen(src));
    if (!yjs_is_exception(result)) {
        char *text = yjs_to_string(ctx, result, NULL);
        printf("%s\n", text); /* hello, yanmajs! */
        yjs_string_free(ctx, text);
    }

    yjs_free(ctx);
    return 0;
}

メモリ上限付きのサンドボックスは yjs_new_with_memory_limit(max_bytes) で作れます(上限超過のアロケーションは OOM と同様に失敗します)。

ホスト関数

C の関数を JavaScript から呼べるようにするには yjs_new_function + yjs_set_global を使います(example/c/host_function.c 参照)。

static YjsValue greet(YjsContext *ctx, YjsValue this_val, int argc,
                      const YjsValue *argv, void *userdata) {
    HostState *state = userdata; /* 登録時の userdata がそのまま届く */
    if (argc < 1) {
        /* C 側から例外を投げる: yjs_throw + センチネル返却の 2 段構え */
        const char *msg = "greet() needs a name argument";
        yjs_throw(ctx, yjs_new_error(ctx, "TypeError", 9, msg, strlen(msg)));
        return yjs_exception();
    }
    char *who = yjs_to_string(ctx, argv[0], NULL);
    printf("hello, %s!\n", who);
    yjs_string_free(ctx, who);
    return yjs_int32(++state->calls);
}

/* 登録(yjs_new_function の戻り値は未ルートなので即座にアタッチする) */
YjsValue fn = yjs_new_function(ctx, "greet", 5, greet, &state);
yjs_set_global(ctx, "greet", 5, fn);
  • argv の各値は呼び出し中のみ有効な借用です。保持するなら yjs_protect してください。
  • 例外を投げる場合は yjs_throw を呼んでから yjs_exception() を返すの両方が必要です。yjs_throw なしでセンチネルを返す動作は未定義です。
  • JS 関数を C から呼ぶには yjs_call(ctx, fn, argc, argv) を使います(v1 では this は undefined 固定。将来 yjs_call_method の追加を予定した命名です)。

API 一覧(グループ別)

グループ 関数
ライフサイクル yjs_new / yjs_new_with_memory_limit / yjs_free / yjs_memory_usage / yjs_collect_garbage
評価・例外 yjs_eval / yjs_eval_module / yjs_is_exception / yjs_get_exception / yjs_exception / yjs_throw
値の生成 yjs_undefined / yjs_null / yjs_bool / yjs_int32 / yjs_float64 / yjs_string / yjs_object / yjs_array / yjs_new_error
検査・変換 yjs_type_tag / yjs_to_bool / yjs_to_int / yjs_to_float / yjs_to_string / yjs_string_free
GC ルート yjs_protect / yjs_unprotect / yjs_persistent_get
プロパティ yjs_get_property / yjs_set_property / yjs_get_index / yjs_set_index / yjs_length
グローバル・関数 yjs_get_global / yjs_set_global / yjs_new_function / yjs_call
Promise yjs_new_promise / yjs_resolve / yjs_reject / yjs_promise_state / yjs_promise_result / yjs_drain_microtasks
中断・制限 yjs_request_interrupt / yjs_clear_interrupt / yjs_set_time_limit_ms
モジュールローダー yjs_set_module_loader
イベントループ yjs_set_event_loop_hook / yjs_fire_timer / yjs_has_pending_timers
コンソール yjs_set_console_handler

コールバック系の補足

  • モジュールローダーYjsModuleLoadFnmalloc() した NUL 終端バッファを返す規約です。エンジンが内容を即座に自前のアロケータへ複製し、元のバッファを free() します(所有権の混在はブリッジ層で遮断されます)。見つからない場合は NULL を返してください。YjsModuleResolveFn(任意)も同じ規約です。
  • イベントループ — due 管理はホスト責務です。参照実装は example/c/event_loop.c(と Zig 版の yanmajs_runtime(yanmajs_runtime/src/event_loop.zigEventLoop))。詳細は組み込みガイドのイベントループ節を参照してください。
  • コンソールYjsConsoleWriteFn に渡る textNUL 終端されておらず、呼び出し中のみ有効な借用です。保持する場合は必ず複製してください。write_fnNULL を渡すと標準出力への既定動作に戻ります。
  • watchdogyjs_set_time_limit_ms(ctx, 100) で各 eval/call に 100ms の壁時計バジェットを課せます(0 で解除)。例は example/c/watchdog.c、仕様は context を参照してください。

サンプル

example/c/ に 4 つの実行可能な例があります(zig build examples-c または make examples-c でビルド+実行)。

ファイル 内容
hello.c eval → 文字列化 → 出力、例外ハンドリングの基本
host_function.c ホスト関数の登録・userdata・C からの例外送出・yjs_call
event_loop.c ホスト駆動イベントループの参照実装(setTimeout/setInterval)
watchdog.c 実行時間制限による暴走スクリプトの打ち切り