Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

C API とクロスランゲージ連携

組み込みガイドでは Zig からの利用方法を紹介しました。しかし zwasm は C ライブラリでもあります — C FFI を持つ任意の言語から WebAssembly モジュールをロード・実行できます。この章では C API のビルド方法、C や Python からの呼び出し方、ホスト関数・WASI・メモリ操作について解説します。

ライブラリのビルド

zig build lib                              # libzwasm をビルド (.dylib / .so / .a)
zig build lib -Doptimize=ReleaseSafe       # 最適化ビルド

出力ファイル:

出力パス
共有ライブラリzig-out/lib/libzwasm.dylib (macOS) または libzwasm.so (Linux)
静的ライブラリzig-out/lib/libzwasm.a
C ヘッダinclude/zwasm.h

ヘッダファイル include/zwasm.h が C API の唯一の定義元です。すべての型は不透明ポインタで、すべての関数は zwasm_ プレフィックスを使用します。

クイックスタート: C

モジュールをロードし、エクスポート関数を呼び出して結果を取得します:

#include <stdio.h>
#include "zwasm.h"

/* Wasm module: (func (export "f") (result i32) (i32.const 42)) */
static const uint8_t WASM[] = {
    0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
    0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f,
    0x03, 0x02, 0x01, 0x00,
    0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00,
    0x0a, 0x06, 0x01, 0x04, 0x00, 0x41, 0x2a, 0x0b
};

int main(void) {
    zwasm_module_t *mod = zwasm_module_new(WASM, sizeof(WASM));
    if (!mod) {
        fprintf(stderr, "Error: %s\n", zwasm_last_error_message());
        return 1;
    }

    uint64_t results[1] = {0};
    if (!zwasm_module_invoke(mod, "f", NULL, 0, results, 1)) {
        fprintf(stderr, "Invoke error: %s\n", zwasm_last_error_message());
        zwasm_module_delete(mod);
        return 1;
    }

    printf("f() = %llu\n", (unsigned long long)results[0]);

    zwasm_module_delete(mod);
    return 0;
}

ビルドと実行:

zig build lib && zig build c-test
./zig-out/bin/example_c_hello
# f() = 42

クイックスタート: Python (ctypes)

Python の組み込み ctypes モジュールを使用した例です。コンパイル済みバインディングは不要です:

import ctypes, os

lib = ctypes.CDLL("zig-out/lib/libzwasm.dylib")  # Linux では .so

# 関数シグネチャの宣言
lib.zwasm_module_new.argtypes = [ctypes.c_char_p, ctypes.c_size_t]
lib.zwasm_module_new.restype = ctypes.c_void_p
lib.zwasm_module_delete.argtypes = [ctypes.c_void_p]
lib.zwasm_module_delete.restype = None
lib.zwasm_module_invoke.argtypes = [
    ctypes.c_void_p, ctypes.c_char_p,
    ctypes.POINTER(ctypes.c_uint64), ctypes.c_uint32,
    ctypes.POINTER(ctypes.c_uint64), ctypes.c_uint32,
]
lib.zwasm_module_invoke.restype = ctypes.c_bool
lib.zwasm_last_error_message.argtypes = []
lib.zwasm_last_error_message.restype = ctypes.c_char_p

# C の例と同じ Wasm バイト列
wasm = bytes([
    0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
    0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f,
    0x03, 0x02, 0x01, 0x00,
    0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00,
    0x0a, 0x06, 0x01, 0x04, 0x00, 0x41, 0x2a, 0x0b,
])

mod = lib.zwasm_module_new(wasm, len(wasm))
assert mod, f"Error: {lib.zwasm_last_error_message().decode()}"

results = (ctypes.c_uint64 * 1)(0)
ok = lib.zwasm_module_invoke(mod, b"f", None, 0, results, 1)
assert ok, f"Invoke error: {lib.zwasm_last_error_message().decode()}"

print(f"f() = {results[0]}")  # f() = 42

lib.zwasm_module_delete(mod)

実行:

zig build lib
python3 examples/python/basic.py

クイックスタート: Rust (FFI)

Rust からも extern "C" バインディングで同じ C API を呼び出せます:

#![allow(unused)]
fn main() {
#[link(name = "zwasm")]
unsafe extern "C" {
    fn zwasm_module_new(wasm_ptr: *const u8, len: usize) -> *mut zwasm_module_t;
    fn zwasm_module_invoke(
        module: *mut zwasm_module_t, name: *const std::ffi::c_char,
        args: *const u64, nargs: u32, results: *mut u64, nresults: u32,
    ) -> bool;
    fn zwasm_module_delete(module: *mut zwasm_module_t);
}
}

ビルドと実行 (Rust 1.85+ が必要、edition 2024):

zig build shared-lib
cd examples/rust && cargo run
# f() = 42

完全な動作例は examples/rust/ を参照してください。

API リファレンス

関数はドメインごとにグループ化されています。すべてのシグネチャは include/zwasm.h に定義されています。

エラーハンドリング

関数説明
zwasm_last_error_message()最後のエラーを null 終端文字列で返す。エラーなしの場合は "" を返す。スレッドローカル。

モジュールのライフサイクル

関数説明
zwasm_module_new(wasm_ptr, len)バイナリバイト列からモジュールを作成。エラー時は NULL
zwasm_module_new_wasi(wasm_ptr, len)デフォルトケーパビリティで WASI モジュールを作成。
zwasm_module_new_wasi_configured(wasm_ptr, len, config)カスタム設定で WASI モジュールを作成。
zwasm_module_new_with_imports(wasm_ptr, len, imports)ホスト関数インポート付きでモジュールを作成。
zwasm_module_delete(module)モジュールの全リソースを解放。
zwasm_module_validate(wasm_ptr, len)インスタンス化せずにバイナリを検証。

関数呼び出し

関数説明
zwasm_module_invoke(module, name, args, nargs, results, nresults)エクスポート関数を名前で呼び出す。
zwasm_module_invoke_start(module)_start(WASI エントリポイント)を呼び出す。

エクスポートの検査

関数説明
zwasm_module_export_count(module)エクスポート関数の数。
zwasm_module_export_name(module, idx)idx 番目のエクスポート名。
zwasm_module_export_param_count(module, idx)エクスポートのパラメータ数。
zwasm_module_export_result_count(module, idx)エクスポートの戻り値数。

メモリアクセス

関数説明
zwasm_module_memory_data(module)リニアメモリへの直接ポインタ。メモリ拡張で無効化される。
zwasm_module_memory_size(module)現在のメモリサイズ(バイト単位)。
zwasm_module_memory_read(module, offset, len, out_buf)境界チェック付き読み出し。
zwasm_module_memory_write(module, offset, data, len)境界チェック付き書き込み。

WASI 設定

関数説明
zwasm_wasi_config_new()設定ハンドルを作成。
zwasm_wasi_config_delete(config)設定ハンドルを解放。
zwasm_wasi_config_set_argv(config, argc, argv)コマンドライン引数を設定。
zwasm_wasi_config_set_env(config, count, keys, key_lens, vals, val_lens)環境変数を設定。
zwasm_wasi_config_preopen_dir(config, host_path, host_len, guest_path, guest_len)ホストディレクトリをマッピング。

ホスト関数インポート

関数説明
zwasm_import_new()インポートコレクションを作成。
zwasm_import_delete(imports)インポートコレクションを解放。
zwasm_import_add_fn(imports, module, name, callback, env, params, results)ホスト関数を登録。

値エンコーディング

Wasm の値は uint64_t 配列として渡されます。エンコーディングは Wasm の生の値表現に対応しています:

Wasm 型C エンコーディング備考
i32uint64_t にゼロ拡張上位 32 ビットはゼロ
i64uint64_t そのまま変換不要
f32IEEE 754 ビットパターンをゼロ拡張float へは memcpy を使用、キャスト不可
f64IEEE 754 ビットパターンを uint64_tdouble へは memcpy を使用、キャスト不可

例 — f64 引数を渡す場合:

double val = 3.14;
uint64_t arg;
memcpy(&arg, &val, sizeof(arg));

uint64_t result[1];
zwasm_module_invoke(mod, "sqrt", &arg, 1, result, 1);

double out;
memcpy(&out, &result[0], sizeof(out));

ホスト関数

ホスト関数は、Wasm モジュールがインポートとして呼び出せる C コールバックです。

コールバックシグネチャ:

typedef bool (*zwasm_host_fn_callback_t)(
    void *env,              /* ユーザーコンテキストポインタ */
    const uint64_t *args,   /* 入力パラメータ */
    uint64_t *results       /* 出力バッファ */
);

動作例 — print_i32 ホスト関数:

#include <stdio.h>
#include "zwasm.h"

static bool print_i32(void *env, const uint64_t *args, uint64_t *results) {
    (void)env;
    (void)results;
    printf("wasm says: %d\n", (int32_t)args[0]);
    return true;
}

int main(void) {
    zwasm_imports_t *imports = zwasm_import_new();
    zwasm_import_add_fn(imports, "env", "print_i32", print_i32, NULL, 1, 0);

    zwasm_module_t *mod = zwasm_module_new_with_imports(wasm_bytes, wasm_len, imports);
    /* ... 呼び出し後にクリーンアップ ... */
    zwasm_module_delete(mod);
    zwasm_import_delete(imports);
}

env ポインタを使用すると、グローバル変数を使わずに任意のコンテキスト(構造体、ファイルハンドルなど)をコールバックに渡せます。

WASI プログラム

設定ビルダーパターンを使用して、カスタム設定で WASI プログラムを実行できます:

/* WASI の設定 */
zwasm_wasi_config_t *config = zwasm_wasi_config_new();

const char *argv[] = {"myapp", "--verbose"};
zwasm_wasi_config_set_argv(config, 2, argv);

zwasm_wasi_config_preopen_dir(config, "/tmp/data", 9, "/data", 5);

/* WASI 設定付きでモジュールを作成 */
zwasm_module_t *mod = zwasm_module_new_wasi_configured(wasm_bytes, wasm_len, config);

/* プログラムを実行 */
zwasm_module_invoke_start(mod);

/* クリーンアップ */
zwasm_module_delete(mod);
zwasm_wasi_config_delete(config);

デフォルトケーパビリティ (stdio, clock, random) のみの単純な WASI プログラムの場合:

zwasm_module_t *mod = zwasm_module_new_wasi(wasm_bytes, wasm_len);
zwasm_module_invoke_start(mod);
zwasm_module_delete(mod);

スレッドセーフティ

  • エラーバッファ: zwasm_last_error_message() はスレッドローカルバッファを返します。複数スレッドからの呼び出しは安全です。
  • モジュール: zwasm_module_t はスレッドセーフではありません。同一モジュールに対して複数スレッドから同時に関数を呼び出さないでください。スレッドごとに個別のモジュールインスタンスを作成してください。

次のステップ

  • ビルド設定 — コンパイルに含める機能のカスタマイズ
  • examples/c/examples/python/examples/rust/ — リポジトリ内の動作する例
  • include/zwasm.h — ドキュメントコメント付きの完全な C ヘッダ