Use a native library to decode an image, process a buffer, or expose your engine's objects to scripts. SCUA supports compiled C bindings and dynamic FFI, where scripts describe and call exported C functions without a wrapper for each function.
The native integration APIs described here are experimental. The expanded dynamic FFI is unreleased development work; no downloaded release binary includes it. Use a source checkout containing the linked examples and an FFI-enabled build.
#Run your first native call
From the repository root on macOS, build SCUA and the supplied example library:
zig build -Dffi=true
zig build ffi-fixtures -Dffi=true
zig cc -std=c11 -Wall -Wextra -Werror -Iinclude examples/ffi_start.c zig-out/lib/libscua.a -lffi -o zig-out/bin/ffi-start
./zig-out/bin/ffi-start "$PWD/zig-out/lib/libscua-ffi-fixture.dylib" examples/ffi_start.scua
The example uses a small library included in the repository; you do
not need raylib. The host
program checks FFI availability, creates a partition, grants the
library as demo, runs the script and frees the partition.
Its command-line path is a choice made by this trusted example host, not
a general SCUA CLI permission.
The native functions have these signatures:
int64_t ffi_add(int64_t a, int64_t b);
void ffi_scale(float *values, size_t count, float factor);
The complete script is:
import ffi
let library = ffi.open("demo")
let add = ffi.bind(library, "ffi_add", "i64", ["i64", "i64"])
assert(ffi.call(add, 20, 22) == 42)
print(ffi.call(add, 20, 22))
let Samples = {type = ffi.pointer("f32"), count_arg = 1}
let scale = ffi.bind(library, "ffi_scale", "void", [Samples, "size_t", "f32"])
let samples: { f32 } = [1.0, 2.0, 3.0]
ffi.call(scale, samples, 3, 2.0)
assert(samples[2] == 6.0)
print(samples[2])
ffi.close(library)
count_arg = 1 associates the pointer with the second C
argument. SCUA checks that the requested element count fits the array
before C runs. C must still respect that count and must not keep the
array address after returning.
Output:
42
6.0
Use zig cc for this source-build example so the linker
matches the Zig-produced static archive. The C host needs no FFI wrapper
functions; it only supplies the library grant and runs the script.
#Choose a binding approach
Use a compiled binding when you want a small application-specific script API, need to adapt a difficult native contract, or want native objects to participate in explicit redacted checkpoints. Register host functions and return opaque userdata values; script authors call those functions without maintaining integer-handle tables.
Use dynamic FFI when the native C signature fits the supported declarations and the caller is trusted. A script can bind strings, buffers, records, output parameters and callbacks. You still need the library's documented signature and ownership rules. Loading a C++ library requires a C-compatible exported interface.
| Your task | Guide |
|---|---|
| Configure grants, bind functions, choose scalar types or variadic signatures | Call native libraries |
| Pass text, image bytes, numeric arrays, records and output parameters | Pass native data |
| Let C invoke a script function or accept an owned callback result | Native callbacks |
| Close resources, opt into cleanup, reconcile native writes, or use unsafe addresses | Resource lifetimes |
| Describe layouts, globals and unions; generate declarations from headers | Binding declarations |
| Return host-owned objects and rebuild resources after a checkpoint | Bind userdata |
| Embed the runtime or drive scripts from an engine | Embedding and engine callbacks |
#Platform support
Dynamic FFI is disabled by default. Build with
-Dffi=true; there is no blanket --allow-ffi
CLI switch, and --allow-all does not grant native
libraries. The browser playground does not support native FFI.
| Target | Current status |
|---|---|
| macOS ARM64 | Focused native execution and interpreter/JIT checks include the latest lifetime and handoff additions. |
| macOS x86-64 | Focused execution under Rosetta includes the latest additions. |
| Linux ARM64 and x86-64 | Existing native fixtures have container execution evidence. The newest lifetime, tracking and handoff additions still need a Linux refresh. Install the target libffi development package and pkg-config. |
| Windows x86-64 | Preview build with -Dffi-windows-preview=true and
-Dffi-prefix=/path/to/target/libffi. Cross-build and Wine
smoke checks pass; real-Windows validation is outstanding. |
| Other targets | Unsupported by this dynamic FFI provider. |
On Linux the example library is libscua-ffi-fixture.so;
host linking may also require -lm -ldl -lpthread. The
commands above are the verified macOS path, not a Windows build recipe.
A library, its dependencies and SCUA must agree on architecture and ABI.
Cross-compiling successfully is not evidence that a target has been
tested.
#Keep native authority separate from saved state
Native code runs with the host process's authority. Grants control which libraries scripts select; they do not sandbox the library or protect against an incorrect signature. Native calls can crash or block indefinitely, outside SCUA's execution and allocation limits. Use a separate process when you need enforceable isolation.
Dynamic FFI handles and type descriptors cannot be sent to actors or persisted, even after close and even in a redacted checkpoint. Remove them from the saved graph and explicitly grant, open and bind again after load. Keep durable asset IDs or connection settings separately from process-local resources.
Compiled userdata has a different contract: both its registered type and the checkpoint operation must opt into redaction. Reload produces a detached value, never a restored pointer. Your application explicitly recreates the resource from durable data.
#Current limits
Callbacks enter SCUA only on the owning thread during an active native call. They are not an asynchronous or audio-thread callback facility. Packed or over-aligned structures can have explicit storage layouts, but general by-value calls require ABI support that is not yet available. Header import supports a defined subset of C and requires manual lifetime annotations. Long double, native SIMD ABI types and flexible array members are not general supported declarations.
The raylib binding repository demonstrates compiled bindings. It is not a promise that dynamic FFI alone can bind every raylib function.