Native Bindings Workflow
This document describes how to use Zynx to generate and manage bindings for existing C libraries, with a focus on ffi modules and bindgen integration.
Overview
Zynx automates much of the C interop workflow:
- Compile local C sources into a static library.
- Generate Zynx bindings from C headers.
- Install the results into
modules/<name>/<target>/for use in your workspace.
Use this workflow when:
- you maintain C sources and headers directly in your project, or
- you vendor a C library into your repository and want tight, explicit bindings.
ffi Modules
Use ffi modules when the C source and headers live directly in your project.
zynx add mylib --kind ffi --header mylib.h --source mylib.c
zynx sync mylib
This workflow:
-
Compiles the local C sources into a static archive.
-
Generates Zynx bindings from the headers using
bindgen. -
Places the artifacts under:
modules/<name>/<target>/
lib<name>.a
<generated-bindings>.zx
.cache/ # bindgen and build metadata
Typical outputs for the example above:
modules/mylib/<target>/libmylib.a- generated
.zxbindings formylib.h - bindgen report data under
modules/mylib/<target>/.cache/
You can then import the generated bindings from your Zynx code via their emitted module path.
Bindgen Flags
bindgen is used internally by ffi modules, and it can also be invoked directly.
When used via ffi modules:
- Flags passed via
--clang-argare forwarded to the internal Clang parser. cFlagsconfigured on anffimodule are applied when compiling the local C source files and when parsing headers, ensuring consistent macro definitions and include paths.
When used directly, you can call:
zynx bindgen -o bindings.zx \
--clang-arg -Iinclude \
--clang-arg -DMODE=1 \
mylib.h
Key options:
-o <out.zx>– Output file for generated Zynx declarations.--clang-arg <arg>– Additional flags for Clang (includes, defines, warning options).--report <path>– Optional report of skipped or partially translated symbols.
For most projects, prefer ffi modules to keep compilation and binding generation in a single, declarative place. Use direct bindgen calls when you need more ad‑hoc or experimental binding generation.
Generated Type Shapes
bindgen preserves C surface types in two primary ways:
-
Tagged records and enums
Emitted as@c_import+@nativedeclarations so that:- field and variant layout matches the C ABI,
- ordering and alignment are preserved.
-
Non‑tag
typedefs
Emitted as transparenttypealiases when the target type is representable in Zynx.
Examples:
type Size = usize;
type PointPtr = *Point;
type PointArray = Point[2];
This keeps C typedef names visible in Zynx signatures without changing ABI or runtime layout. Call sites can use the familiar C names without paying any runtime cost.
Where C types cannot be represented directly (for example, exotic bitfield patterns), bindgen will fall back to the closest safe representation or omit the symbol, depending on configuration. Use --report to inspect these cases.
Struct ABI Compatibility
For tagged C structs, Zynx types generated by bindgen use @native. In Zynx, @native also implies strict field order for layout, which ensures:
- Declaration order is preserved.
- The compiler does not reorder or re‑pack fields.
- The in‑memory layout remains compatible with the C ABI.
As a result:
- Passing such structs by value or reference across the Zynx/C boundary is ABI‑safe, as long as both sides agree on the platform and compiler configuration.
- You should not add additional attributes that change layout (
@packed, custom alignment) on top of generated declarations unless you also update the C side accordingly.
When in doubt, keep generated ABI surface types unchanged and layer higher‑level Zynx types on top if you need different ergonomics.
Package Interop Boundary
C interop and package management are related but distinct concerns:
-
package
Zynx libraries resolved from registries or git. They are consumed via the package system and lockfile. -
ffi
Local C code that must be compiled and bound into the current project, typically living in the same repository as your Zynx workspace.
Registry and git package dependencies are designed for Zynx code built from source (or provided as binary artifacts). They are not a replacement for ffi modules:
- Use
packagefor Zynx library projects resolved by semver. - Use
ffifor local C code that must be compiled and bound into the current project.
A typical setup is:
- One or more Zynx packages for higher‑level logic and reusable libraries.
- One or more
ffimodules that provide bindings to platform libraries or vendored C code. - Packages that depend on
ffimodules at the workspace level, not the other way around.
Keeping this boundary clear makes builds more predictable and keeps ABI‑sensitive C integration localized to well‑defined modules.