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 & Cross-Language Integration

The Embedding Guide showed how to use zwasm from Zig. But zwasm is also a C library — any language with a C FFI can load and run WebAssembly modules through it. This chapter covers the C API: building the shared library, calling it from C and Python, and working with host functions, WASI, and memory.

Building the library

zig build lib                              # Build libzwasm (.dylib / .so / .a)
zig build lib -Doptimize=ReleaseSafe       # Optimized build

This produces:

OutputPath
Shared libraryzig-out/lib/libzwasm.dylib (macOS) or libzwasm.so (Linux)
Static libraryzig-out/lib/libzwasm.a
C headerinclude/zwasm.h

The header file include/zwasm.h is the single source of truth for the C API. All types are opaque pointers; all functions use the zwasm_ prefix.

Quickstart: C

Load a module, invoke an exported function, and read the result:

#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;
}

Build and run:

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

Quickstart: Python (ctypes)

The same workflow using Python’s built-in ctypes module — no compiled bindings required:

import ctypes, os

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

# Declare function signatures
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

# Same Wasm bytes as the C example
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)

Run:

zig build lib
python3 examples/python/basic.py

API reference

Functions are grouped by domain. All signatures live in include/zwasm.h.

Error handling

FunctionDescription
zwasm_last_error_message()Last error as a null-terminated string. Returns "" if no error. Thread-local.

Module lifecycle

FunctionDescription
zwasm_module_new(wasm_ptr, len)Create module from binary bytes. Returns NULL on error.
zwasm_module_new_wasi(wasm_ptr, len)Create WASI module with default capabilities.
zwasm_module_new_wasi_configured(wasm_ptr, len, config)Create WASI module with custom config.
zwasm_module_new_with_imports(wasm_ptr, len, imports)Create module with host function imports.
zwasm_module_delete(module)Free all module resources.
zwasm_module_validate(wasm_ptr, len)Validate binary without instantiation.

Function invocation

FunctionDescription
zwasm_module_invoke(module, name, args, nargs, results, nresults)Invoke an exported function by name.
zwasm_module_invoke_start(module)Invoke _start (WASI entry point).

Export introspection

FunctionDescription
zwasm_module_export_count(module)Number of exported functions.
zwasm_module_export_name(module, idx)Name of the idx-th export.
zwasm_module_export_param_count(module, idx)Parameter count of an export.
zwasm_module_export_result_count(module, idx)Result count of an export.

Memory access

FunctionDescription
zwasm_module_memory_data(module)Direct pointer to linear memory. Invalidated by growth.
zwasm_module_memory_size(module)Current memory size in bytes.
zwasm_module_memory_read(module, offset, len, out_buf)Safe bounded read.
zwasm_module_memory_write(module, offset, data, len)Safe bounded write.

WASI configuration

FunctionDescription
zwasm_wasi_config_new()Create a config handle.
zwasm_wasi_config_delete(config)Free a config handle.
zwasm_wasi_config_set_argv(config, argc, argv)Set command-line arguments.
zwasm_wasi_config_set_env(config, count, keys, key_lens, vals, val_lens)Set environment variables.
zwasm_wasi_config_preopen_dir(config, host_path, host_len, guest_path, guest_len)Map a host directory.

Host function imports

FunctionDescription
zwasm_import_new()Create an import collection.
zwasm_import_delete(imports)Free an import collection.
zwasm_import_add_fn(imports, module, name, callback, env, params, results)Register a host function.

Value encoding

Wasm values are passed as uint64_t arrays. The encoding matches the raw Wasm value representation:

Wasm typeC encodingNotes
i32Zero-extended to uint64_tUpper 32 bits are zero
i64Direct uint64_tNo conversion needed
f32IEEE 754 bits, zero-extendedUse memcpy to a float, not a cast
f64IEEE 754 bits as uint64_tUse memcpy to a double, not a cast

Example — passing an f64 argument:

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));

Host functions

A host function is a C callback that the Wasm module can call as an import.

Callback signature:

typedef bool (*zwasm_host_fn_callback_t)(
    void *env,              /* User context pointer */
    const uint64_t *args,   /* Input parameters */
    uint64_t *results       /* Output buffer */
);

Working example — a print_i32 host function:

#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);
    /* ... invoke, then cleanup ... */
    zwasm_module_delete(mod);
    zwasm_import_delete(imports);
}

The env pointer lets you pass arbitrary context (a struct, file handle, etc.) to the callback without globals.

WASI programs

Use the config builder pattern to run WASI programs with custom settings:

/* Create and configure 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);

/* Create module with WASI config */
zwasm_module_t *mod = zwasm_module_new_wasi_configured(wasm_bytes, wasm_len, config);

/* Run the program */
zwasm_module_invoke_start(mod);

/* Cleanup */
zwasm_module_delete(mod);
zwasm_wasi_config_delete(config);

For simple WASI programs that only need default capabilities (stdio, clock, random):

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

Thread safety

  • Error buffer: zwasm_last_error_message() returns a thread-local buffer. Safe to call from multiple threads.
  • Modules: A zwasm_module_t is not thread-safe. Do not invoke functions on the same module from multiple threads concurrently. Create separate module instances per thread instead.

Next steps

  • Build Configuration — customize which features are compiled in
  • examples/c/ and examples/python/ — working examples in the repository
  • include/zwasm.h — the complete C header with doc comments