infix
A JIT-Powered FFI Library for C
Loading...
Searching...
No Matches
infix: A JIT-Powered FFI Library for C

CI (Matrix)

infix is a modern, security-conscious, and dependency-free Foreign Function Interface (FFI) library for C. It simplifies the process of calling native C functions from other environments and creating C-callable function pointers from your own handlers. All with a simple, human-readable string like ({int, *double}, *char) -> int.

At its core, infix is a Just-in-Time (JIT) compiler that generates tiny, highly-optimized machine code "trampolines" at runtime. These trampolines correctly handle the low-level Application Binary Interface (ABI) for the target platform, ensuring seamless and performant interoperability.

Who is this for?

infix is designed for developers who need to bridge the gap between different codebases or language runtimes. You'll find it especially useful if you are:

  • A Language Binding Author: infix is the ideal engine for allowing a high-level language like Python, Ruby, Perl, or Lua to call C libraries. The introspectable type system simplifies the complex task of data marshalling.
  • A Plugin System Architect: Build a stable, ABI-agnostic plugin system. infix can provide the boundary layer, allowing you to load and call functions from shared libraries without tight coupling.
  • A C/C++ Developer: Dynamically call functions from system libraries (user32.dll, libc.so.6, etc.) without needing to link against them at compile time, or create complex stateful callbacks for C APIs.
  • A Security Researcher: infix provides a powerful, fuzz-tested toolkit for analyzing and interacting with native code.

Key Features

  • Zero Dependencies & Simple Integration: infix uses a unity build, making integration into any C/C++ project trivial by simply compiling src/infix.c.
  • Simple, Powerful APIs: Use the high-level Signature API to create trampolines from a single string, or drop down to the memory-safe Manual API for dynamic, performance-critical use cases.
  • Advanced Type System: Full support for primitives, pointers, structs, unions, arrays, enums, _Complex numbers, and SIMD vectors.
  • Named Type Registry: Define complex types like structs and unions once, and reuse them by name (@Name) across all your signatures for unparalleled readability and maintainability.
  • Stateful Callbacks Made Easy: The reverse-call API is designed to make stateful callbacks simple and safe, even when the C library you're calling doesn't provide a user_data parameter.
  • Secure by Design: infix is hardened against vulnerabilities and validated through extensive fuzz testing:
    • W^X Memory Protection: JIT-compiled code is never writable and executable at the same time.
    • Guard Pages: Freed trampolines are made inaccessible to prevent use-after-free bugs.
    • Read-Only Contexts: Callback context data is made read-only to guard against runtime memory corruption.
  • Cross-Platform and Cross-Architecture: Designed for portability, with initial support for x86-64 (System V and Windows x64) and AArch64 (AAPCS64).
  • Arena-Based Memory: Utilizes an efficient arena allocator for all type descriptions, ensuring fast performance and leak-free memory management.
  • Dynamic Library Tools: A cross-platform API to load shared libraries (.so, .dll, .dylib), look up symbols, and read/write global variables using the same powerful signature system.

Getting Started

Prerequisites

  • A C11-compatible compiler (GCC, Clang, or MSVC).
  • (Optional) A build tool like cmake, xmake, make, etc.

Building the Library

While you can use the provided build scripts, the simplest way to build infix is to compile its single translation unit directly.

# Build a static library on Linux/macOS
gcc -c -std=c11 -O2 -I/path/to/infix/include src/infix.c -o infix.o
ar rcs libinfix.a infix.o
# Build a static library with MSVC
cl.exe /c /I C:\path\to\infix\include /O2 src\infix.c /Foinfix.obj
lib.exe /OUT:infix.lib infix.obj

Integrating into Your Project

  1. Include the Header:

    c #include <infix/infix.h>

  2. Link the Library: When compiling your application, link against the libinfix.a (or infix.lib) library.

    bash gcc my_app.c -I/path/to/infix/include -L/path/to/build/dir -linfix -o my_app

Quick Start: A 60-Second Example

Here is a complete, runnable example that calls the standard C library function puts.

#include <stdio.h>
#include <infix/infix.h>
int main() {
// 1. Describe the function signature: int puts(const char*);
const char* signature = "(*char) -> int32";
// 2. Create a "bound" trampoline, hardcoding the address of `puts`.
// Pass nullptr for the registry as we are not using named types.
infix_forward_t* trampoline = NULL;
infix_forward_create(&trampoline, signature, (void*)puts, NULL);
// 3. Get the callable function pointer.
// 4. Prepare arguments and call.
// The `args` array must contain *pointers* to your argument values.
const char* my_string = "Hello from infix!";
void* args[] = { &my_string };
int return_value;
cif(&return_value, args); // A non-negative value is returned on success.
printf("puts returned: %d\n", return_value);
// 5. Clean up.
infix_forward_destroy(trampoline);
return 0;
}
void * args[]
Definition 202_in_structs.c:75
int main(void)
Definition 821_threading_bare.c:102
c23_nodiscard infix_status infix_forward_create(infix_forward_t **, const char *, void *, infix_registry_t *)
Generates a bound forward-call trampoline from a signature string.
Definition signature.c:1202
c23_nodiscard infix_cif_func infix_forward_get_code(infix_forward_t *)
Retrieves the executable code pointer from a bound forward trampoline.
Definition trampoline.c:223
void infix_forward_destroy(infix_forward_t *)
Frees a forward trampoline and its associated executable memory.
Definition trampoline.c:400
void(* infix_cif_func)(void *, void **)
The signature for a "bound" forward-call trampoline.
Definition infix.h:335
Definition infix_internals.h:72

Usage Guide

Part 1: The Signature Language

The signature language is the most powerful and convenient way to use infix.

Name infix Syntax Example Signature C/C++ Equivalent
Primitives C type names "int", "double", "uint64" int, double, uint64_t
Pointers *<type> "*int", "*void" int*, void*
Structs {<members>} "{int, double, *char}" struct { ... }
Unions <<members>> "<int, float>" union { ... }
Arrays [<size>:<type>] "[10:double]" double[10]
Function Pointers (<args>)-><ret> "(int, int)->int" int (*)(int, int)
**_Complex** c[<base_type>] "c[double]" _Complex double
SIMD Vectors v[<size>:<type>] "v[4:float]" __m128, float32x4_t
Enums e:<int_type> "e:int" enum { ... }
Packed Structs !{...} or !<N>:{...} "!{char, longlong}" __attribute__((packed))
Variadic Functions (<fixed>;<variadic>) "(*char; int)->int" printf(const char*, ...)
Named Types @Name or @NS::Name "@Point", "@UI::User" typedef struct Point {...}
Named Arguments <name>:<type> "(count:int, data:*void)" (For reflection only)

See the complete signature specification.

Part 2: Common Recipes

A wide range of recipes may be found in infix's cookbook.

Forward Call (Calling C from your code)

#include <stdio.h>
#include <infix/infix.h>
int main() {
// 1. Describe the function signature: int puts(const char*);
const char* signature = "(*char) -> int32";
// 2. Create a "bound" trampoline, hardcoding the address of `puts`.
// Pass nullptr for the registry as we are not using named types.
infix_forward_t* trampoline = NULL;
infix_forward_create(&trampoline, signature, (void*)puts, nullptr);
// 3. Get the callable function pointer.
// 4. Prepare arguments and call.
const char* my_string = "Hello from infix!";
void* args[] = { &my_string };
int return_value;
cif(&return_value, args);
printf("puts returned: %d\n", return_value);
// 5. Clean up.
infix_forward_destroy(trampoline);
return 0;
}

Reverse Call (Creating a C callback)

// 1. The custom handler function. Its signature must start with infix_context_t*.
int my_adder_handler(infix_context_t* context, int a, int b) {
(void)context; // Unused in this simple example
return a + b;
}
// 2. The native C code that will receive and call our callback.
void run_callback(int (*func_ptr)(int, int)) {
int result = func_ptr(20, 22);
printf("Native code received result: %d\n", result); // Prints 42
}
int main() {
// 3. Create the reverse trampoline (the callback).
infix_reverse_t* context = NULL;
const char* signature = "(int32, int32) -> int32";
infix_reverse_create(&context, signature, (void*)my_adder_handler, NULL, NULL);
// 4. Get the native C function pointer and pass it to the C code.
typedef int (*AdderFunc)(int, int);
run_callback((AdderFunc)infix_reverse_get_code(context));
// 5. Clean up.
return 0;
}
c23_nodiscard infix_status infix_reverse_create(infix_reverse_t **, const char *, void *, void *, infix_registry_t *)
Generates a reverse-call trampoline (callback) from a signature string.
Definition signature.c:1239
c23_nodiscard void * infix_reverse_get_code(const infix_reverse_t *)
Retrieves the executable code pointer from a reverse trampoline.
Definition trampoline.c:586
void infix_reverse_destroy(infix_reverse_t *)
Frees a reverse trampoline, its JIT-compiled stub, and its context.
Definition trampoline.c:607
Definition infix_internals.h:102

Using the Named Type Registry

// 1. Create a registry.
// 2. Define your types as a semicolon-separated string.
const char* my_types =
"@UserID = uint64;" // Create a readable alias.
"@UI::Point = { x: double, y: double };" // Define a struct in a namespace.
"@Node = { value: int, next: *@Node };"; // Define a recursive linked-list node.
// 3. Register the types.
infix_register_types(registry, my_types);
// 4. Use the named types in any signature by passing the registry.
infix_forward_t* trampoline = NULL;
// Assume `get_user_id_from_node` is a C function you want to call.
// infix_forward_create(&trampoline, "(*@Node) -> @UserID", (void*)get_user_id_from_node, registry);
// 5. Clean up.
void infix_registry_destroy(infix_registry_t *registry)
Frees a type registry and all type definitions and metadata contained within it.
Definition type_registry.c:166
c23_nodiscard infix_status infix_register_types(infix_registry_t *, const char *)
Parses a string of definitions and populates a type registry.
Definition type_registry.c:233
c23_nodiscard infix_registry_t * infix_registry_create(void)
Creates a new, empty type registry.
Definition type_registry.c:131
Definition infix_internals.h:157

Reading Global Variables from a Shared Library

infix can read and write to global variables exported from a dynamic library.

**Library Code (libglobals.c):**

#if defined(_WIN32)
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
EXPORT int my_global_counter = 42;

Compile this into libglobals.so or libglobals.dll.

Main Application Code:

#include <infix/infix.h>
#include <stdio.h>
void main() {
infix_library_t* lib = infix_library_open("./libglobals.so");
if (!lib) return;
int counter_value = 0;
// 1. Use a signature to describe the variable's type.
infix_status status = infix_read_global(lib, "my_global_counter", "int32", &counter_value);
printf("Initial global value: %d\n", counter_value); // Expected: 42
}
// 2. Write a new value.
int new_value = 100;
infix_write_global(lib, "my_global_counter", "int32", &new_value);
// 3. Read it back to confirm.
infix_read_global(lib, "my_global_counter", "int32", &counter_value);
printf("Updated global value: %d\n", counter_value); // Expected: 100
}
infix_status status
Definition 103_unions.c:74
infix_status infix_write_global(infix_library_t *, const char *, const char *, void *)
Writes a value to a global variable in a loaded library.
Definition loader.c:213
infix_status infix_read_global(infix_library_t *, const char *, const char *, void *)
Reads the value of a global variable from a loaded library.
Definition loader.c:170
infix_status
An enumeration of all possible success or failure codes from the public API.
Definition infix.h:340
@ INFIX_SUCCESS
The operation completed successfully.
Definition infix.h:341
void infix_library_close(infix_library_t *lib)
Definition loader.c:113
c23_nodiscard infix_library_t * infix_library_open(const char *path)
Definition loader.c:65
Definition loader.c:49

Part 3: The Manual C API (Advanced)

For dynamic use cases, you can build infix_type objects programmatically. All types are allocated from an infix_arena_t.

#include <stddef.h> // For offsetof
typedef struct { double x; double y; } Point; // C struct for reference
void build_point_manually() {
infix_type* point_type = NULL;
// Now `point_type` can be used to create trampolines.
infix_arena_destroy(arena); // Frees the arena and all types within it.
}
infix_arena_t * arena
Definition 005_layouts.c:57
infix_struct_member * members
Definition 103_unions.c:68
void infix_arena_destroy(infix_arena_t *)
Frees an entire memory arena and all objects allocated within it.
Definition arena.c:68
c23_nodiscard infix_arena_t * infix_arena_create(size_t)
Creates and initializes a new memory arena.
Definition arena.c:41
c23_nodiscard infix_status infix_type_create_struct(infix_arena_t *, infix_type **, infix_struct_member *, size_t)
Creates a new infix_type for a struct from an arena.
Definition types.c:445
c23_nodiscard infix_type * infix_type_create_primitive(infix_primitive_type_id)
Creates an infix_type descriptor for a primitive C type.
Definition types.c:97
infix_struct_member infix_type_create_member(const char *, infix_type *, size_t)
A factory function to create an infix_struct_member.
Definition types.c:161
@ INFIX_PRIMITIVE_DOUBLE
double
Definition infix.h:146
A simple struct with two doubles (16 bytes).
Definition types.h:39
Definition infix_internals.h:129
Describes a single member of an aggregate type (struct or union).
Definition infix.h:219
The central structure for describing any data type in the FFI system.
Definition infix.h:161

Powerful Introspection for Dynamic Data Marshalling

Beyond just calling functions, infix provides a powerful introspection API that allows you to parse a signature string and examine the complete memory layout of a C type at runtime. This is the key feature that makes infix an ideal engine for building language bindings, serializers, or any tool that needs to dynamically interact with C data structures.

Example: Inspecting a C Struct at Runtime

#include <infix/infix.h>
#include <stdio.h>
// The C struct we want to understand.
typedef struct {
int32_t user_id;
double score;
const char* name;
} UserProfile;
int main() {
// 1. A signature describing the C struct, with named fields.
const char* profile_sig = "{id:int32, score:double, name:*char}";
// 2. Parse the signature to get a detailed, introspectable type object.
if (infix_type_from_signature(&struct_type, &arena, profile_sig, nullptr) != INFIX_SUCCESS) {
return 1;
}
// 3. Use the introspection API to query the layout.
printf("Inspecting struct layout for: %s\n", profile_sig);
printf("Total size: %zu bytes, Alignment: %zu bytes\n",
for (size_t i = 0; i < infix_type_get_member_count(struct_type); ++i) {
printf(" - Member '%s': offset=%zu, size=%zu\n",
member->name,
member->offset,
}
// 4. Clean up the parser's temporary memory.
return 0;
}
infix_type * struct_type
Definition 202_in_structs.c:64
c23_nodiscard infix_status infix_type_from_signature(infix_type **, infix_arena_t **, const char *, infix_registry_t *)
Parses a signature string representing a single data type.
Definition signature.c:1090
c23_nodiscard size_t infix_type_get_size(const infix_type *)
Retrieves the size of an infix_type in bytes.
Definition types.c:728
c23_nodiscard size_t infix_type_get_alignment(const infix_type *)
Retrieves the alignment requirement of an infix_type in bytes.
Definition types.c:736
c23_nodiscard size_t infix_type_get_member_count(const infix_type *)
Retrieves the number of members in an aggregate type (struct or union).
Definition types.c:745
c23_nodiscard const infix_struct_member * infix_type_get_member(const infix_type *, size_t)
Retrieves a specific member from an aggregate type by its index.
Definition types.c:756
const char * name
The name of the member (for debugging/reflection).
Definition infix.h:220
infix_type * type
An infix_type describing the member's type.
Definition infix.h:221
size_t offset
The byte offset of the member from the start of the aggregate.
Definition infix.h:222

Output on a typical 64-bit system:

Inspecting struct layout for: {id:int32, score:double, name:*char}
Total size: 24 bytes, Alignment: 8 bytes
- Member 'id': offset=0, size=4
- Member 'score': offset=8, size=8
- Member 'name': offset=16, size=8

This runtime layout information allows you to, for example, take a Perl hash and correctly pack its key/value pairs into a C UserProfile struct in memory, byte by byte.

Error Handling

Nearly all infix API functions return an infix_status enum. If an operation fails, you can get detailed, thread-safe error information.

infix_forward_t* trampoline = NULL;
// This will fail if `registry` is NULL or doesn't contain `@MissingType`.
// infix_status status = infix_forward_create(&trampoline, "(@MissingType)->void", my_func, registry);
fprintf(stderr, "Error creating trampoline!\n");
fprintf(stderr, " Category: %d\n", err.category); // e.g., INFIX_CATEGORY_PARSER
fprintf(stderr, " Code: %d\n", err.code); // e.g., INFIX_CODE_UNRESOLVED_NAMED_TYPE
fprintf(stderr, " Position: %zu\n", err.position); // Byte offset in signature string
}
infix_error_details_t infix_get_last_error(void)
Retrieves detailed information about the last error that occurred on the current thread.
Definition error.c:87
A structure holding detailed information about the last error that occurred on the current thread.
Definition infix.h:1027
size_t position
For parser errors, the 0-based index in the input string where the error occurred.
Definition infix.h:1030
infix_error_category_t category
The general category of the error.
Definition infix.h:1028
infix_error_code_t code
The specific error code.
Definition infix.h:1029

API Reference

A brief overview of the complete public API, grouped by functionality.

Click to expand Full API Reference

Named Type Registry (<tt>registry_api</tt>)

High-Level Signature API (<tt>high_level_api</tt>)

Dynamic Library & Globals API (<tt>exports_api</tt>)

Manual API (<tt>manual_api</tt>)

Type System (<tt>type_system</tt>)

Memory Management (<tt>memory_management</tt>)

Introspection API (<tt>introspection_api</tt>)

Error Handling API (<tt>error_api</tt>)

Building and Integrating

Full build instructions for xmake, cmake, GNU make, and other systems are available in INSTALL.md.

Because infix uses a unity build, integration into an existing project is simple: add src/infix.c to your list of source files and add the include/ directory to your include paths.

Supported Platforms

infix is rigorously tested on a wide array of operating systems, compilers, and architectures with every commit.

Click to expand Full Platform CI Results

OS Version Architecture Compiler Status
DragonflyBSD 6.4.0 x86-64 GCC dragonflybsd/x64/gcc
FreeBSD 14.3 x86-64 GCC freebsd/x86/gcc
14.3 AArch64 GCC freebsd/a64/gcc
14.3 x86-64 Clang freebsd/x64/clang
14.3 AArch64 Clang freebsd/a64/clang
macOS 15 AArch64 Clang macos/a64/clang
15 AArch64 GCC macos/a64/gcc
NetBSD 10.1 AArch64 GCC netbsd/a64/gcc
10.1 x86-64 GCC netbsd/x64/gcc
OmniOS r151052 x86-64 GCC omnios/x64/gcc
OpenBSD 7.7 AArch64 Clang openbsd/a64/clang
7.7 AArch64 GCC openbsd/a64/gcc
7.7 x86-64 Clang openbsd/x64/clang
7.7 x86-64 Clang openbsd/x64/clang
Solaris 11.4 x86-64 GCC solaris/x64/gcc
Ubuntu 24.04 AArch64 Clang ubuntu/a64/clang
24.04 AArch64 GCC ubuntu/a64/gcc
24.04 x86-64 Clang ubuntu/x64/clang
24.04 x86-64 GCC ubuntu/x64/gcc
Windows Server 2025 AArch64 Clang windows/a64/clang
Server 2025 AArch64 GCC windows/a64/gcc
Server 2025 AArch64 MSVC windows/a64/msvc
Server 2025 x86-64 Clang windows/x64/clang
Server 2025 x86-64 GCC windows/x64/gcc
Server 2025 x86-64 MSVC windows/x64/msvc

In addition to the CI platforms tested here on Github, I can verify infix builds and passes unit tests on Android/Termux.

Contributing

Contributions are welcome! Please feel free to submit a pull request or open an issue for any bugs, feature requests, or documentation improvements.

Learn More

  • Cookbook: Practical, copy-pasteable recipes for common FFI tasks.
  • Signature Reference: The complete guide to the signature mini-language.
  • Internals: A deep dive into the library's architecture, JIT engine, and security features.
  • Porting Guide: Instructions for adding support for new architectures.
  • INSTALL.md: Detailed build and integration instructions.

License & Legal

infix is provided under multiple licenses to maximize its usability for all.

Code License

Source code, including header files (.h) and implementation files (.c), is dual-licensed under the Artistic License 2.0 or the MIT License. You may choose to use the code under the terms of either license.

See the [LICENSE-A2](LICENSE-A2) and/or [LICENSE-MIT](LICENSE-MIT) for the full text of both licenses.

Documentation License

At your discretion, all standalone documentation (.md), explanatory text, Doxygen-style documentation blocks, comments, and code examples contained within this repository may be used, modified, and distributed under the terms of the Creative Commons Attribution 4.0 International License (CC BY 4.0). We encourage you to share and adapt the documentation for any purpose (generating an API reference website, creating tutorials, etc.), as long as you give appropriate credit.

See the [LICENSE-CC](LICENSE-CC) for details.