This guide provides practical, real-world examples to help you solve common FFI problems and leverage the full power of the infix library. Where the README.md covers concepts, this cookbook provides the code.
Note: For a complete reference on the string format used in these examples (e.g., "int", "{double, double}", "*char"), please see the Signature Language Reference.
Table of Contents
- Chapter 1: The Basics (Forward Calls)
- Recipe: Calling a Simple C Function
- Recipe: Passing and Receiving Pointers
- Recipe: Working with "Out" Parameters
- Recipe: Working with Opaque Pointers (Incomplete Types)
- Chapter 2: Handling Complex Data Structures
- Recipe: Small Structs Passed by Value
- Recipe: Receiving a Struct from a Function
- Recipe: Large Structs Passed by Reference
- Recipe: Working with Packed Structs
- Recipe: Working with Structs that Contain Bitfields
- Recipe: Working with Unions
- Recipe: Working with Fixed-Size Arrays
- Recipe: Advanced Named Types (Recursive & Forward-Declared)
- Recipe: Working with Complex Numbers
- Recipe: Working with SIMD Vectors
- x86-64 (SSE, AVX, and AVX-512)
- AArch64 (NEON)
- AArch64 (Scalable Vector Extension - SVE)
- Recipe: Working with Enums
- Chapter 3: The Power of Callbacks (Reverse Calls)
- Recipe: Creating a Type-Safe Callback for `qsort`
- Recipe: Creating a Stateful Callback
- Chapter 4: Advanced Techniques
- Recipe: Calling Variadic Functions like `printf`
- Recipe: Receiving and Calling a Function Pointer
- Recipe: Calling a Function Pointer from a Struct (V-Table Emulation)
- Recipe: Handling `long double`
- Recipe: Proving Reentrancy with Nested FFI Calls
- Recipe: Proving Thread Safety
- Chapter 5: Interoperability with Other Languages
- The Universal Principle: The C ABI
- Recipe: Interfacing with a C++ Class (Directly)
- Recipe: Interfacing with C++ Templates
- The Pattern for Other Compiled Languages
- Rust
- Zig
- Go
- Swift
- Dlang
- Fortran
- Assembly
- Recipe: Handling Strings and Semantic Types (`wchar_t`, etc.)
- Recipe: Calling C++ Virtual Functions (V-Table Emulation)
- Recipe: Bridging C++ Callbacks (`std::function`) and Lambdas
- Chapter 6: Dynamic Libraries & System Calls
- Recipe: Calling Native System Libraries without Linking
- Recipe: Reading and Writing Global Variables
- Example 1: Simple Integer Variable
- Example 2: Aggregate (Struct) Variable
- Recipe: Handling Library Dependencies
- Chapter 7: Introspection for Data Marshalling
- Recipe: Creating and Introspecting Semantic Aliases
- Recipe: Dynamic Struct Marshalling with the Signature Parser
- Recipe: Building a Signature String at Runtime
- Recipe: Introspecting a Trampoline for a Wrapper
- Chapter 8: Performance & Memory Management
- Best Practice: Caching Trampolines
- Recipe: Using a Custom Arena for a Group of Types
- Recipe: The Full Manual API Lifecycle (Types to Trampoline)
- Recipe: Using Custom Memory Allocators
- Recipe: Optimizing Memory with a Shared Arena
- Recipe: Building a Dynamic Call Frame with an Arena
- How It Works & Why It's Better
- Advanced Optimization: Arena Resetting for Hot Loops
- Chapter 9: Common Pitfalls & Troubleshooting
- Recipe: Advanced Error Reporting for the Parser
- Mistake: Passing a Value Instead of a Pointer in `args[]`
- Mistake: `infix` Signature Mismatch
- Pitfall: Function Pointer Syntax
- Chapter 10: A Comparative Look: `infix` vs. `libffi` and `dyncall`
- Scenario 1: Calling a Simple Function
- The `dyncall` Approach
- The `libffi` Approach
- The `infix` Approach
- Scenario 2: Calling a Function with a Struct
- The `dyncall` Approach
- The `libffi` Approach
- The `infix` Approach
- Scenario 3: Creating a Callback
- The `dyncall` Approach
- The `libffi` Approach
- The `infix` Approach
- Analysis and Takeaways
- Chapter 11: Building Language Bindings
- The Four Pillars of a Language Binding
- Recipe: Porting a Python Binding from `dyncall` to `infix`
Chapter 1: The Basics (Forward Calls)
Recipe: Calling a Simple C Function
Problem: You want to call a standard C function, like atan2 from the math library.
Solution: Describe the function's signature, prepare pointers to your arguments, and invoke the function through a generated trampoline. An "unbound" trampoline is ideal when you want to call multiple functions that share the same signature.
const char* signature = "(double, double) -> double";
double y = 1.0, x = 1.0;
void*
args[] = { &y, &x };
double result;
void * args[]
Definition 202_in_structs.c:64
c23_nodiscard infix_status infix_forward_create_unbound(infix_forward_t **, const char *, infix_registry_t *)
Creates an "unbound" forward trampoline from a signature string.
Definition trampoline.c:926
c23_nodiscard infix_unbound_cif_func infix_forward_get_unbound_code(infix_forward_t *)
Gets the callable function pointer from an unbound forward trampoline.
Definition trampoline.c:282
Internal definition of a forward trampoline handle.
Definition infix_internals.h:94
Full example available in Ch01_SimpleCall.c.
Recipe: Passing and Receiving Pointers
Problem: You need to call a C function that takes a pointer as an argument and returns a pointer, like strchr.
Solution: Use the * prefix for pointer types. The value in the args array for a pointer argument is the address of your pointer variable.
const char* signature = "(*char, int) -> *char";
const char* haystack = "hello-world";
int needle = '-';
void*
args[] = { &haystack, &needle };
const char* result_ptr = NULL;
c23_nodiscard infix_status infix_forward_create(infix_forward_t **, const char *, void *, infix_registry_t *)
Creates a "bound" forward trampoline from a signature string.
Definition trampoline.c:919
c23_nodiscard infix_cif_func infix_forward_get_code(infix_forward_t *)
Gets the callable function pointer from a bound forward trampoline.
Definition trampoline.c:288
Full example available in Ch01_Pointers.c.
Recipe: Working with "Out" Parameters
Problem: You need to call a C function that doesn't use its return value for its primary output. Instead, it takes a pointer to a variable and writes the result into it. This is a very common pattern for functions that need to return multiple values or an error code.
Solution: The signature is straightforward. The "out" parameter is simply a pointer type (*<type>). In your calling code, you create a local variable and pass its address in the args array.
const char* signature = "(int, *int, *double) -> bool";
int user_id = 123;
int post_count = 0;
double score = 0.0;
bool success;
int* p_post_count = &post_count;
double* p_score = &score;
void*
args[] = { &user_id, &p_post_count, &p_score };
static bool get_user_stats(int user_id, int *out_posts, double *out_score)
Definition Ch01_OutParameters.c:16
Full example available in Ch01_OutParameters.c.
Recipe: Working with Opaque Pointers (Incomplete Types)
Problem: You need to interact with a C library that uses opaque pointers or handles (e.g., FILE*, sqlite3*) where the internal structure is hidden.
Solution: Use the *void signature for the handle. For better readability and introspection, create a "semantic alias" for the handle type in a registry. This attaches a meaningful name to the *void type without changing how it's handled in the FFI call.
void* file_handle = NULL;
const char* filename = "test.txt", *mode = "w";
c23_nodiscard infix_status infix_register_types(infix_registry_t *, const char *)
Parses a string of type definitions and adds them to a registry.
Definition type_registry.c:460
c23_nodiscard infix_registry_t * infix_registry_create(void)
Creates a new, empty named type registry.
Definition type_registry.c:156
Internal definition of a named type registry.
Definition infix_internals.h:175
Full example available in Ch01_OpaquePointers.c.
</blockquote>
Chapter 2: Handling Complex Data Structures
typedef struct {
double x, y; }
Point;
A simple struct with two doubles, often used to test pass-by-value on registers.
Definition types.h:22
Recipe: Small Structs Passed by Value
Problem: You need to call a function that takes a small struct that the ABI passes in registers. Solution: Use the struct syntax ({...}). infix will automatically determine the correct ABI passing convention for the target platform.
const char* signature = "({double, double}, double) -> {double, double}";
double delta_x = 5.5;
Point move_point(Point p, double dx, double dy)
A C function to be called via a forward trampoline. Takes and returns a struct.
Definition 006_end_to_end_calls.c:35
clock_t start
Definition 901_call_overhead.c:50
clock_t end
Definition 901_call_overhead.c:50
Full example available in Ch02_StructByValue.c.
Recipe: Receiving a Struct from a Function
Problem: You need to call a function that returns a struct by value.
Solution: infix handles the ABI details, whether the struct is returned in registers or via a hidden pointer passed by the caller.
const char* signature = "(double, double) -> {double, double}";
double x = 100.0, y = 200.0;
void*
args[] = { &x, &y };
static Point make_point(double x, double y)
Definition Ch02_ReturnStruct.c:19
Full example available in Ch02_ReturnStruct.c.
Recipe: Large Structs Passed by Reference
Problem: A function takes a struct that is too large to fit in registers.
Solution: The process is identical to the small struct example. infix's ABI logic will detect that the struct is large and automatically pass it by reference (the standard C ABI rule).
const char* signature = "({[8:int]}) -> int";
LargeStruct my_struct = { {123, -1, -1, -1, -1, -1, -1, -1} };
void*
args[] = { &my_struct };
int result;
static int get_first_element(LargeStruct s)
Definition Ch02_LargeStruct.c:23
A struct larger than 16 bytes, guaranteed to be passed by reference on most ABIs.
Definition types.h:44
Full example available in Ch02_LargeStruct.c.
Recipe: Working with Packed Structs
Problem: You need to call a function that takes a __attribute__((packed)) struct.
Solution: Use the !{...} syntax for 1-byte alignment, or !N:{...} to specify a maximum alignment of N bytes.
const char* signature = "(!{char, uint64}) -> int";
Packed p = {
'X', 0x1122334455667788ULL};
int result = 0;
static int process_packed(Packed p)
Definition Ch02_PackedStructs.c:25
Definition Ch02_PackedStructs.c:18
Full example available in Ch02_PackedStructs.c.
Recipe: Working with Structs that Contain Bitfields
Problem: You need to interact with a C struct that uses bitfields. infix's signature language has no syntax for bitfields because their memory layout is implementation-defined and not portable.
Solution: Treat the underlying integer that holds the bitfields as a single member in your signature. Then, use C's bitwise operators in your wrapper code to manually pack and unpack the values before and after the FFI call.
const char* signature = "({uint32}) -> uint32";
uint32_t packed_data = 0;
packed_data |= (15 & 0xF) << 0;
packed_data |= (1000 & 0xFFF) << 4;
packed_data |= (30000 & 0xFFFF) << 16;
void*
args[] = { &packed_data };
uint32_t result;
static uint32_t process_bitfields(BitfieldStruct s)
Definition Ch02_Bitfields.c:25
Full example available in Ch02_Bitfields.c.
Recipe: Working with Unions
Problem: You need to call a function that passes or returns a union.
Solution: Use the <...> syntax to describe the union's members.
const char* signature = "(<int, float>) -> int";
int result = 0;
void*
args[] = {&num_val};
static int process_number_as_int(Number n)
Definition Ch02_Unions.c:20
A simple union to test aggregate classification.
Definition types.h:38
int i
Definition types.h:39
Full example available in Ch02_Unions.c.
Recipe: Working with Fixed-Size Arrays
Problem: A function takes a fixed-size array, like long long sum(long long arr[4]);.
Solution: In C, an array argument "decays" to a pointer to its first element. The signature must reflect this. To describe the array itself (e.g., inside a struct), use the [N:type] syntax.
const char* signature = "(*sint64, size_t) -> sint64";
int64_t my_array[] = {10, 20, 30, 40};
const int64_t* ptr_to_array = my_array;
size_t count = 4;
void*
args[] = {&ptr_to_array, &count};
int64_t result = 0;
static int64_t sum_array_elements(const int64_t *arr, size_t count)
Definition Ch02_ArrayDecay.c:21
Full example available in Ch02_ArrayDecay.c.
Recipe: Advanced Named Types (Recursive & Forward-Declared)
Problem: You need to describe complex, real-world C data structures, such as a linked list or mutually dependent types.
Solution: The infix registry fully supports recursive definitions and forward declarations, allowing you to model these patterns cleanly.
"@Employee; @Manager;"
"@Manager = { name:*char, reports:[10:*@Employee] };"
"@Employee = { name:*char, manager:*@Manager };"
;
Manager boss = {
"Sanko", { NULL } };
Employee worker = {
"Robinson", &boss };
const char* manager_name = NULL;
void*
args[] = { &p_worker };
const char * definitions
Definition 008_registry_introspection.c:39
infix_registry_t * registry
Definition 008_registry_introspection.c:35
static const char * get_manager_name(Employee *e)
Definition Ch02_AdvancedRegistry.c:26
Definition Ch02_AdvancedRegistry.c:20
Definition Ch02_AdvancedRegistry.c:15
Full example available in Ch02_AdvancedRegistry.c.
Recipe: Working with Complex Numbers
Problem: You need to call a C function that uses _Complex types.
Solution: Use the c[<base_type>] constructor in the signature string.
const char* signature = "(c[double]) -> c[double]";
double complex input = 3.0 + 4.0 * I;
double complex result;
static double complex c_square(double complex z)
Definition Ch02_ComplexNumbers.c:21
Full example available in Ch02_ComplexNumbers.c.
Recipe: Working with SIMD Vectors
Problem: You need to call a high-performance C function that uses architecture-specific SIMD vector types for parallel data processing.
Solution: Use the generic v[<elements>:<type>] syntax or, where available, a convenient keyword alias (like m512d). The infix ABI logic contains the specific rules for each platform to ensure that these vectors are correctly passed in the appropriate SIMD registers (e.g., XMM/YMM/ZMM on x86-64, V/Z registers on AArch64).
This recipe is broken down by architecture, as the C types and intrinsic functions are platform-specific.
x86-64 (SSE, AVX, and AVX-512)
This example calls a function that uses AVX-512's 512-bit __m512d type to add two vectors of eight doubles each. The same principle applies to 128-bit SSE (__m128d) and 256-bit AVX (__m256d) types by simply adjusting the signature.
const char* signature = "(m512d, m512d) -> m512d";
__m512d a = _mm512_set_pd(8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0);
__m512d b = _mm512_set_pd(34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0);
Definition Ch02_SIMD_AVX.c:23
Full example available in Ch02_SIMD_AVX.c.
</blockquote>
AArch64 (NEON)
This example calls a function that uses ARM NEON's float32x4_t type, which is a 128-bit vector of four floats.
const char* signature = "(v[4:float]) -> float";
float data[] = {10.0f, 20.0f, 5.5f, 6.5f};
float32x4_t input_vec = vld1q_f32(data);
void*
args[] = {&input_vec};
float result;
Full example available in Ch02_SIMD_NEON.c.
</blockquote>
AArch64 (Scalable Vector Extension - SVE)
Problem: SVE vectors do not have a fixed size; their length is determined by the CPU at runtime. How can we create a trampoline for a function that uses them?
Solution: This requires a dynamic, multi-step approach. You must first perform a runtime check for SVE support, then query the CPU's vector length, and finally build the infix signature string dynamically before creating the trampoline.
size_t num_doubles = svcntd();
char signature[64];
snprintf(signature, sizeof(signature), "(v[%zu:double]) -> double", num_doubles);
double* data = (double*)malloc(sizeof(double) * num_doubles);
svfloat64_t input_vec = svld1_f64(svptrue_b64(), data);
double result;
Full example available in Ch02_SIMD_SVE.c.
Recipe: Working with Enums
Problem: You need to call a C function that takes an enum, but you want to ensure the underlying integer type is handled correctly for ABI purposes.
Solution: Use the e:<type> syntax in the signature string. infix treats the enum identically to its underlying integer type for the FFI call, which is the correct behavior.
const char* signature = "(e:int) -> *char";
const char* result_str = NULL;
void*
args[] = { &code };
static const char * status_to_string(StatusCode code)
Definition Ch02_Enums.c:19
@ STATUS_ERR
Definition Ch02_Enums.c:17
Full example available in Ch02_Enums.c.
</blockquote>
Chapter 3: The Power of Callbacks (Reverse Calls)
Recipe: Creating a Type-Safe Callback for <tt>qsort</tt>
Problem: You need to sort an array using C's qsort, which requires a function pointer for the comparison logic.
Solution: Use infix_reverse_create_callback. The handler is a normal, clean C function whose signature exactly matches what qsort expects.
return (*(const int*)a - *(const int*)b);
}
const char* cmp_sig = "(*void, *void) -> int";
typedef int (*compare_func_t)(const void*, const void*);
int numbers[] = { 5, 1, 4, 2, 3 };
qsort(numbers, 5, sizeof(int), my_comparator);
static int compare_integers_handler(const void *a, const void *b)
Definition Ch03_QsortCallback.c:19
c23_nodiscard infix_status infix_reverse_create_callback(infix_reverse_t **, const char *, void *, infix_registry_t *)
Creates a type-safe reverse trampoline (callback).
Definition trampoline.c:932
c23_nodiscard void * infix_reverse_get_code(const infix_reverse_t *)
Gets the native, callable C function pointer from a reverse trampoline.
Definition trampoline.c:813
Internal definition of a reverse trampoline (callback/closure) handle.
Definition infix_internals.h:121
Full example available in Ch03_QsortCallback.c.
Recipe: Creating a Stateful Callback
Problem: A callback handler needs access to application state, but the C library API is stateless (it has no void* user_data parameter).
Solution: Use infix_reverse_create_closure. This API is specifically designed for stateful callbacks. You provide a generic handler and a void* user_data pointer to your state. Inside the handler, you can retrieve this pointer from the context.
int item_value = *(
int*)
args[0];
}
int list[] = {10, 20, 30};
static void my_stateful_handler(infix_context_t *context, void *ret, void **args)
Definition Ch03_StatefulCallback.c:25
void(* item_processor_t)(int)
Definition Ch03_StatefulCallback.c:39
static void process_list(const int *items, int count, item_processor_t process_func)
Definition Ch03_StatefulCallback.c:40
c23_nodiscard infix_status infix_reverse_create_closure(infix_reverse_t **, const char *, infix_closure_handler_fn, void *, infix_registry_t *)
Creates a generic reverse trampoline (closure) for stateful callbacks.
Definition trampoline.c:962
c23_nodiscard void * infix_reverse_get_user_data(const infix_reverse_t *)
Gets the user-provided data pointer from a closure context.
Definition trampoline.c:824
Definition Ch03_StatefulCallback.c:18
int sum
Definition Ch03_StatefulCallback.c:20
Full example available in Ch03_StatefulCallback.c.
</blockquote>
Chapter 4: Advanced Techniques
Recipe: Calling Variadic Functions like <tt>printf</tt>
Problem: You need to call a function with a variable number of arguments.
Solution: Use the ; token to separate fixed and variadic arguments. The signature must exactly match the types you are passing in a specific call.
const char* signature = "(*char; int, double) -> int";
const char* fmt = "Count: %d, Value: %.2f\n";
int count = 42;
double value = 123.45;
void*
args[] = { &fmt, &count, &value };
int result;
Full example available in Ch04_VariadicPrintf.c.
Recipe: Receiving and Calling a Function Pointer
Problem: You need to call a C function that takes a function pointer as an argument, and pass it a callback you generate.
Solution: The signature for a function pointer is *((...) -> ...). Generate your callback, get its native pointer, and pass that pointer as an argument.
const char* harness_sig = "(*((int)->int), int) -> int";
int value = 7;
void* harness_args[] = { &inner_cb_ptr, &value };
int result;
static int multiply_handler(int x)
Definition Ch04_CallbackAsArg.c:16
static int harness_func(int(*worker_func)(int), int base_val)
Definition Ch04_CallbackAsArg.c:22
Full example available in Ch04_CallbackAsArg.c.
Recipe: Calling a Function Pointer from a Struct (V-Table Emulation)
Problem: You have a pointer to a struct that contains function pointers, similar to a C implementation of an object's v-table. You need to read a function pointer from the struct and then call it.
Solution: This is a two-step FFI process. First, read the function pointer value from the struct. Second, create a new trampoline for that function pointer's signature and call it. The Type Registry is perfect for making this clean.
"@Adder = { val: int };"
"@Adder_add_fn = (*@Adder, int) -> int;"
"@AdderVTable = { add: *@Adder_add_fn };"
);
void* add_func_ptr = (
void*)vtable->
add;
int amount_to_add = 23, result;
void* add_args[] = { &my_adder, &amount_to_add };
const AdderVTable VTABLE
Definition Ch04_VTableCStyle.c:44
static Adder * create_adder(int base)
Definition Ch04_VTableCStyle.c:47
Definition Ch04_VTableCStyle.c:38
int(* add)(Adder *self, int amount)
Definition Ch04_VTableCStyle.c:39
Definition Ch04_VTableCStyle.c:19
Full example available in Ch04_VTableCStyle.c.
Recipe: Handling <tt>long double</tt>
Problem: You need to call a function that uses long double, which has different sizes and ABI rules on different platforms (e.g., 80-bit on x86, 128-bit on AArch64, or just an alias for double on MSVC/macOS).
Solution: Use the longdouble keyword in your signature. infix's ABI logic contains the platform-specific rules to handle it correctly, whether it's passed on the x87 FPU stack (System V x64), in a 128-bit vector register (AArch64), or as a normal double.
const char* signature = "(longdouble) -> longdouble";
long double input = 144.0L;
long double result = 0.0L;
void*
args[] = { &input };
static long double native_sqrtl(long double x)
Definition Ch04_LongDouble.c:21
Full example available in Ch04_LongDouble.c.
Recipe: Proving Reentrancy with Nested FFI Calls
Problem: You need to be sure that making an FFI call from within an FFI callback is safe.
Solution: infix is designed to be fully reentrant. The library uses no global mutable state, and all error information is stored in thread-local storage. This recipe demonstrates a forward call that invokes a reverse callback, which in turn makes another forward call.
const char* harness_sig = "(*((int)->int), int)->int";
int base_val = 8;
void* harness_args[] = { &callback_ptr, &base_val };
int final_result;
void harness(int(*func_ptr)(int, int), int a, int b, int expected)
Definition 304_reverse_call_types.c:33
static void nested_call_handler(infix_context_t *ctx, void *ret, void **args)
Definition Ch04_Reentrancy.c:29
static int multiply(int a, int b)
Definition Ch04_Reentrancy.c:22
Full example available in Ch04_Reentrancy.c.
Recipe: Proving Thread Safety
Problem: You need to create a trampoline in one thread and safely use it in another.
Solution: infix trampoline handles (infix_forward_t* and infix_reverse_t*) are immutable after creation and are safe to share between threads. All error state is kept in thread-local storage, so calls from different threads will not interfere with each other.
pthread_t thread_id;
pthread_join(thread_id, NULL);
static int add(int a, int b)
Definition Ch04_ThreadSafety.c:25
void * worker_thread_func(void *arg)
Definition Ch04_ThreadSafety.c:37
Definition Ch04_ThreadSafety.c:28
Full example available in Ch04_ThreadSafety.c.
</blockquote>
Chapter 5: Interoperability with Other Languages
The Universal Principle: The C ABI
infix can call any function that exposes a standard C ABI. Nearly every compiled language provides a mechanism to export a function using this standard (extern "C" in C++/Rust/Zig, //export in Go, bind(C) in Fortran).
Recipe: Interfacing with a C++ Class (Directly)
Problem: You need to call C++ class methods without writing a C wrapper.
Solution: Find the compiler-mangled names for the constructor, destructor, and methods. Use infix to call them directly, manually passing the this pointer as the first argument to methods.
const char* mangled_ctor = get_mangled_constructor();
const char* mangled_getvalue = get_mangled_getvalue();
void* obj = malloc(obj_size);
int initial_val = 100;
int result;
Full example available in Ch05_CppMangledNames.c and library source in libs/MyClass.cpp.
Recipe: Interfacing with C++ Templates
Problem: How do you call a C++ function template from C?
Solution: You can't call the template itself, but you can call a specific instantiation of it. The compiler generates a normal function for each concrete type used with the template, and this function has a predictable mangled name that you can look up and call.
double val = 3.14;
void* my_box = malloc(sizeof(double));
memcpy(my_box, &val, sizeof(double));
double result;
const char * MANGLED_GET_DBL
Definition Ch05_CppTemplates.c:15
c23_nodiscard void * infix_library_get_symbol(infix_library_t *, const char *)
Retrieves the address of a symbol (function or variable) from a loaded library.
Definition loader.c:121
Full example available in Ch05_CppTemplates.c and library source in libs/Box.cpp.
The Pattern for Other Compiled Languages
The extern "C" pattern is universal. The C code to call any of the functions below would be identical: load the library, find the symbol, create a trampoline for (int, int) -> int, and call it.
Rust
To export a C-compatible function from Rust, use #[no_mangle] to prevent name mangling and extern "C" to specify the calling convention.
// librust_math.rs
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
a + b
}
Compile with: rustc --crate-type cdylib librust_math.rs
Zig
Zig's export keyword makes a function available with the C ABI by default.
// libzig_math.zig
export fn zig_add(a: c_int, b: c_int) c_int {
return a + b;
}
Compile with: zig build-lib -dynamic libzig_math.zig
Go
Go can export functions to C using a special //export comment directive.
// libgo_math.go
package main
import "C"
//export go_add
func go_add(a C.int, b C.int) C.int { return a + b }
func main() {}
Compile with: go build -buildmode=c-shared -o libgo_math.so libgo_math.go
Swift
The @_cdecl attribute exposes a Swift function to C with a specified name.
// libswift_math.swift
@_cdecl("swift_add")
public func swift_add(a: CInt, b: CInt) -> CInt {
return a + b
}
Compile with: swiftc -emit-library libswift_math.swift -o libswift_math.so
Dlang
The extern(C) attribute specifies the C calling convention for a D function.
extern (C) int d_add(int a, int b) { return a + b; }
Compile with: dmd -shared -fPIC -of=libd_math.so libd_math.d
Fortran
The bind(C) attribute from the iso_c_binding module provides C interoperability.
function fortran_add(a, b) result(c) bind(C, name='fortran_add')
use iso_c_binding
integer(c_int), value :: a, b
integer(c_int) :: c
c = a + b
end function fortran_add
Compile with: gfortran -shared -fPIC -o libfortran_math.so libfortran_math.f90
Assembly
Pure machine code has no name mangling. You just need to follow the target ABI's calling convention.
; libasm_math.asm (for System V x64 ABI)
section .text
global asm_add
asm_add:
mov eax, edi ; Move first argument (RDI) into EAX
add eax, esi ; Add second argument (RSI) to EAX
ret ; Return value is in EAX
Compile with: nasm -f elf64 libasm_math.asm && gcc -shared -o libasm_math.so libasm_math.o
Recipe: Handling Strings and Semantic Types (<tt>wchar_t</tt>, etc.)
Problem: You are building a language binding and need to introspect a type to marshal data correctly. A signature like *uint16 is structurally correct for a Windows wchar_t*, but how does your code know it's a null-terminated string and not just a pointer to an integer?
Solution: Use the infix type registry to create semantic aliases. The new infix_type_get_name() API allows you to retrieve this semantic name at runtime, giving your wrapper the context it needs to perform the correct marshalling logic. This pattern provides the missing link between C's structural type system and the semantic needs of a high-level language.
Let's model the Windows MessageBoxW function, which takes two UTF-16 strings.
const char* type_defs =
"@HWND = *void;"
"@UTF16String = *uint16;"
"@UTF8String = *char;";
const char* signature = "(@HWND, @UTF16String, @UTF16String, uint) -> int";
if (semantic_name && strcmp(semantic_name, "UTF16String") == 0) {
}
infix_arena_t * arena
Definition 005_layouts.c:68
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:1026
c23_nodiscard const char * infix_type_get_name(const infix_type *)
Gets the semantic alias of a type, if one exists.
Definition types.c:1100
c23_nodiscard const infix_type * infix_forward_get_arg_type(const infix_forward_t *, size_t)
Gets the type of a specific argument for a forward trampoline.
Definition types.c:1214
A semi-opaque structure that describes a C type.
Definition infix.h:211
Full example available in Ch05_SemanticStrings.c.
Recipe: Calling C++ Virtual Functions (V-Table Emulation)
Problem: You have a pointer to a C++ polymorphic base class object (e.g., Shape*) and you need to call a virtual function on it from C, achieving true dynamic dispatch.
Solution: Emulate what the C++ compiler does: manually read the object's v-table pointer (vptr), find the function pointer at the correct index within the v-table, and use infix to call it.
void* rect_obj = create_rectangle(10.0, 5.0);
void** vptr = (void**)rect_obj;
void** vtable = *vptr;
void* area_fn_ptr = vtable[0];
void* name_fn_ptr = vtable[1];
double rect_area;
Full example available in Ch05_CppVirtualFunctions.c and library source in libs/shapes.cpp.
Recipe: Bridging C++ Callbacks (<tt>std::function</tt>) and Lambdas
Problem: You need to call a C++ method that accepts a callback (e.g., std::function<void(int)>), and provide a stateful handler from your C application.
Solution: This is a powerful, two-way FFI interaction. You will create an infix closure to represent your C-side state, and then call a mangled C++ method to register that closure's components (its C function pointer and its state pointer) with the C++ object.
C_AppState app_state = {0};
void* manager_obj = malloc(get_size());
const char* sig = "(*void, *((int, *void)->void), *void)->void";
void* set_handler_args[] = { &manager_obj, &closure_c_func, &closure_user_data };
Full example available in Ch05_CppCallbacks.cpp and library source in libs/EventManager.cpp.
Chapter 6: Dynamic Libraries & System Calls
Recipe: Calling Native System Libraries without Linking
Problem: You need to call a function from a system library (e.g., user32.dll) without linking against its import library at compile time.
Solution: Use infix's cross-platform library loading API to get a handle to the library and the function pointer, then create a trampoline.
const char* sig = "(*void, *char, *char, uint32) -> int";
void* hwnd = NULL;
const char* text = "Hello from a dynamically loaded function!";
int result;
c23_nodiscard infix_library_t * infix_library_open(const char *)
Opens a dynamic library and returns a handle to it.
Definition loader.c:50
Internal definition of a dynamic library handle.
Definition infix_internals.h:218
Full example available in Ch06_SystemLibraries.c.
Recipe: Reading and Writing Global Variables
Problem: You need to access a global variable exported from a shared library, not just a function.
Solution: Use infix_read_global() and infix_write_global(). The powerful signature language is used to describe the variable's type, ensuring infix reads or writes the correct number of bytes.
Example 1: Simple Integer Variable
int counter_val = 0;
int new_val = 100;
c23_nodiscard infix_status infix_write_global(infix_library_t *, const char *, const char *, void *, infix_registry_t *)
Writes data from a buffer into a global variable in a library.
Definition loader.c:196
c23_nodiscard infix_status infix_read_global(infix_library_t *, const char *, const char *, void *, infix_registry_t *)
Reads the value of a global variable from a library into a buffer.
Definition loader.c:149
Example 2: Aggregate (Struct) Variable
Config new_config = {
"updated", 2 };
Definition Ch06_GlobalVariables.c:15
Full example available in Ch06_GlobalVariables.c and library source in libs/libglobals.c.
Recipe: Handling Library Dependencies
Problem: You want to load a library (libA) that itself depends on another shared library (libB).
Solution: You don't have to do anything special. On all modern operating systems, the dynamic linker will automatically find, load, and link libB when you load libA.
Full example available in Ch06_LibraryDependencies.c and library sources in libs/libA.c and libs/libB.c.
</blockquote>
Chapter 7: Introspection for Data Marshalling
Recipe: Creating and Introspecting Semantic Aliases
Problem: You need to distinguish between types that are structurally identical (like multiple kinds of void* handles) for your language binding's marshalling logic.
Solution: Use the type registry to create semantic aliases. The infix_type_get_name() function allows you to retrieve these names at runtime, providing the context your code needs.
"@DatabaseHandle = *void;"
"@IteratorHandle = *void;"
"@MyInt = int32;"
);
if (name && strcmp(name, "DatabaseHandle") == 0) {
}
}
c23_nodiscard infix_type_category infix_type_get_category(const infix_type *)
Gets the fundamental category of a type.
Definition types.c:1111
@ INFIX_TYPE_POINTER
Definition infix.h:164
Internal definition of a memory arena.
Definition infix_internals.h:146
Full example available in Ch07_SemanticAliases.c.
Recipe: Dynamic Struct Marshalling with the Signature Parser
Problem: You have data from a dynamic source (like a script) and need to pack it into a C struct layout at runtime.
Solution: Use infix_type_from_signature to parse a signature into a detailed infix_type graph. This graph contains all the size, alignment, and member offset information needed to correctly write data into a C-compatible memory buffer.
}
}
static void marshal_ordered_data(void *dest, const char *sig, void **src)
A generic marshalling function that packs data into a struct.
Definition Ch07_DynamicMarshalling.c:33
infix_type * type
Definition infix.h:279
size_t offset
Definition infix.h:280
void infix_arena_destroy(infix_arena_t *)
Destroys an arena and frees all memory allocated from it.
Definition arena.c:90
c23_nodiscard size_t infix_type_get_size(const infix_type *)
Gets the size of a type in bytes.
Definition types.c:1120
c23_nodiscard size_t infix_type_get_member_count(const infix_type *)
Gets the number of members in a struct or union type.
Definition types.c:1135
c23_nodiscard const infix_struct_member * infix_type_get_member(const infix_type *, size_t)
Gets a specific member from a struct or union type.
Definition types.c:1147
Describes a single member of a C struct or union.
Definition infix.h:277
Full example available in Ch07_DynamicMarshalling.c.
Recipe: Building a Signature String at Runtime
Problem: The structure of the data you need to work with isn't known until runtime (e.g., it's defined in a configuration file or a user script).
Solution: Since infix signatures are just strings, you can build them dynamically using snprintf. You can then parse this dynamic signature to get layout information, which is perfect for data marshalling or dynamic RPC systems.
const char* user_defined_fields[] = { "int", "int", "double" };
int num_fields = 3;
char signature_buffer = "{";
for (int i = 0; i < num_fields; ++i) {
strcat(signature_buffer, user_defined_fields[i]);
if (i < num_fields - 1) strcat(signature_buffer, ",");
}
strcat(signature_buffer, "}");
Full example available in Ch07_DynamicSignatures.c.
Recipe: Introspecting a Trampoline for a Wrapper
Problem: You are building a language binding and need to validate the number and types of arguments provided by the user before making an FFI call.
Solution: Use the trampoline introspection API to query the signature information stored in the handle.
if (num_provided_args != num_expected_args) {
fprintf(stderr, "Error: Incorrect number of arguments...\n");
return;
}
cif(target_func, NULL,
args);
}
static bool dynamic_wrapper(infix_forward_t *trampoline, void *target_func, void **args, size_t num_provided_args, void *ret_buffer)
A conceptual generic "wrapper" function for an unbound trampoline.
Definition Ch07_IntrospectWrapper.c:29
void(* infix_unbound_cif_func)(void *, void *, void **)
A function pointer type for an unbound forward trampoline.
Definition infix.h:361
c23_nodiscard size_t infix_forward_get_num_args(const infix_forward_t *)
Gets the total number of arguments for a forward trampoline.
Definition types.c:1186
Full example available in Ch07_IntrospectWrapper.c.
</blockquote>
Chapter 8: Performance & Memory Management
Best Practice: Caching Trampolines
Rule: NEVER generate a new trampoline for the same function signature inside a hot loop. The performance of infix comes from amortizing the one-time generation cost over many fast calls.
int result;
for (int i = 0; i < 1000000; ++i) {
}
void(* infix_cif_func)(void *, void **)
A function pointer type for a bound forward trampoline.
Definition infix.h:371
void infix_forward_destroy(infix_forward_t *)
Destroys a forward trampoline and frees all associated memory.
Definition trampoline.c:523
Recipe: Using a Custom Arena for a Group of Types
Goal: Create a set of related infix_type objects for the Manual API and free them all at once.
void recipe_custom_arena() {
}
c23_nodiscard infix_arena_t * infix_arena_create(size_t)
Creates a new memory arena.
Definition arena.c:55
c23_nodiscard infix_status infix_type_create_array(infix_arena_t *, infix_type **, infix_type *, size_t)
Creates a new fixed-size array type.
Definition types.c:299
c23_nodiscard infix_type * infix_type_create_primitive(infix_primitive_type_id)
Creates a static descriptor for a primitive C type.
Definition types.c:138
@ INFIX_PRIMITIVE_SINT32
Definition infix.h:186
Recipe: The Full Manual API Lifecycle (Types to Trampoline)
Problem: You want to create a trampoline without using the signature parser, for maximum performance or because your type information is already structured in C.
Solution: Use an arena to build your infix_type objects and then pass them directly to the _manual variant of the creation functions.
};
infix_type * arg_types[]
Definition 901_call_overhead.c:67
c23_nodiscard infix_status infix_forward_create_manual(infix_forward_t **, infix_type *, infix_type **, size_t, size_t, void *)
Creates a bound forward trampoline from infix_type objects.
Definition trampoline.c:479
c23_nodiscard infix_status infix_type_create_struct(infix_arena_t *, infix_type **, infix_struct_member *, size_t)
Creates a new struct type from an array of members, calculating layout automatically.
Definition types.c:490
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:202
@ INFIX_PRIMITIVE_DOUBLE
Definition infix.h:192
Full example available in Ch08_ManualAPI.c.
Recipe: Using Custom Memory Allocators
Problem: Your application uses a custom memory manager for tracking, pooling, or integration with a garbage collector. You need infix to use your allocators instead of the standard malloc, calloc, etc.
Solution: infix provides override macros (infix_malloc, infix_free, etc.). Define these macros before you include infix.h to redirect all of its internal memory operations.
#define infix_malloc(size) tracking_malloc(size)
#define infix_free(ptr) tracking_free(ptr)
void dummy_func()
Definition 851_lifecycle_regression.c:35
void * tracking_malloc(size_t size)
Definition Ch08_CustomAllocators.c:20
void tracking_free(void *ptr)
Definition Ch08_CustomAllocators.c:28
The public interface for the infix FFI library.
Full example available in Ch08_CustomAllocators.c.
Recipe: Optimizing Memory with a Shared Arena
Problem: Your application creates a large number of trampolines that all reference the same set of complex, named types (e.g., @Point, @User). By default, infix deep-copies the metadata for these types into each trampoline's private memory, leading to high memory consumption and slower creation times.
Solution: Use the shared arena pattern. By creating the type registry and all related trampolines within a single, user-managed arena, you instruct infix to share pointers to the canonical named types instead of copying them. This drastically reduces memory usage and speeds up trampoline creation, but it requires you to manage the lifetime of the shared arena carefully.
When to use it: This is an advanced pattern ideal for language runtimes, plugin systems, or long-running applications that create many FFI interfaces referencing a common set of C headers.
#include <stdio.h>
#include <stdint.h>
typedef struct {
double x;
double y; }
Point;
typedef struct { uint64_t id;
const char* name; }
User;
void shared_arena_example() {
const char* my_types =
"@Point = { x: double, y: double };"
"@User = { id: uint64, name: *char };";
}
void handle_point(const Point *p)
Definition Ch08_SharedArena.c:34
void handle_user(const User *u)
Definition Ch08_SharedArena.c:41
c23_nodiscard infix_status infix_forward_create_in_arena(infix_forward_t **, infix_arena_t *, const char *, void *, infix_registry_t *)
Creates a "bound" forward trampoline within a user-provided arena.
Definition trampoline.c:832
void infix_registry_destroy(infix_registry_t *)
Destroys a type registry and frees all associated memory.
Definition type_registry.c:230
c23_nodiscard infix_registry_t * infix_registry_create_in_arena(infix_arena_t *arena)
Creates a new named type registry that allocates from a user-provided arena.
Definition type_registry.c:196
Definition Ch08_SharedArena.c:29
Full, runnable example available in Ch08_SharedArena.c.
Recipe: Building a Dynamic Call Frame with an Arena
Problem: You are writing a language binding (e.g., for Python, Perl, Lua) and need to build the void* args[] array at runtime. The arguments are coming from the host language, so you need to unbox them into temporary C values, create an array of pointers to these temporary values, and then clean everything up after the call. Doing this with malloc for every call in a tight loop is inefficient.
Solution: Use an infix arena to allocate memory for both the unboxed C values and the void** array that points to them. This makes the entire call frame a single, contiguous block of memory that can be allocated and freed with extreme efficiency.
for (int i = 0; i < arg_count; ++i) {
*val_ptr = va_arg(va, int);
}
}
static void dynamic_ffi_call(infix_forward_t *trampoline, void *target_func, int arg_count,...)
Definition Ch08_ArenaCallFrame.c:31
c23_nodiscard void * infix_arena_alloc(infix_arena_t *, size_t, size_t)
Allocates a block of memory from an arena.
Definition arena.c:126
Full example available in Ch08_ArenaCallFrame.c.
How It Works & Why It's Better
- Unified Allocation: Instead of multiple calls to
malloc (one for the args array, one for the int, one for the double, etc.), all memory is sourced from a single arena.
- Performance: The allocations within the arena are extremely fast "bump" allocations, which is significantly cheaper than heap allocation, especially for many small objects.
- Simplified Cleanup: All temporary data for the call—the
void** array and the unboxed C values—lives in the arena. A single call to infix_arena_destroy cleans everything up instantly, eliminating the risk of memory leaks from forgetting to free one of the many small allocations.
Advanced Optimization: Arena Resetting for Hot Loops
For a binding that needs to make many FFI calls in a tight loop, you can achieve even higher performance by creating the arena once outside the loop and "resetting" it on each iteration. Since infix_arena_t is not an opaque type, you can do this manually:
for (int i = 0; i < 1000; ++i) {
}
size_t current_offset
Definition infix_internals.h:149
Chapter 9: Common Pitfalls & Troubleshooting
Recipe: Advanced Error Reporting for the Parser
Problem: A user provides an invalid signature string, and you want to give them a helpful error message indicating exactly where the syntax error occurred.
Solution: After a parsing function fails, call infix_get_last_error() and use the position, code, and message fields to generate a detailed diagnostic.
fprintf(stderr, "Failed to parse signature:\n");
fprintf(stderr, " %s\n", signature);
fprintf(stderr,
" %*s^\n", (
int)err.
position,
"");
fprintf(stderr, "Error: %s (code: %d, position: %zu)\n",
}
}
infix_status status
Definition 103_unions.c:66
static void report_parse_error(const char *signature)
A helper function that attempts to parse a signature and reports detailed error information on failur...
Definition Ch09_ErrorReporting.c:21
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:270
size_t position
Definition infix.h:1363
char message[256]
Definition infix.h:1365
infix_status
Enumerates the possible status codes returned by infix API functions.
Definition infix.h:389
infix_error_code_t code
Definition infix.h:1362
@ INFIX_SUCCESS
Definition infix.h:390
Provides detailed, thread-local information about the last error that occurred.
Definition infix.h:1360
Full example available in Ch09_ErrorReporting.c.
Mistake: Passing a Value Instead of a Pointer in <tt>args[]</tt>
- Symptom: Crash or garbage data.
- Explanation: The
args array for a forward call must be an array of pointers to your argument values, not the values themselves.
Mistake: <tt>infix</tt> Signature Mismatch
- Symptom: Silent data corruption, garbage values, or a crash.
- Explanation: The signature string must exactly match the C type's size and alignment. A
long is 32 bits on 64-bit Windows but 64 bits on 64-bit Linux.
- Solution: Use fixed-width types (
int32, uint64) whenever possible.
Pitfall: Function Pointer Syntax
- Symptom: Parser error.
- Explanation: A function type is
(...) -> ..., and a pointer is *.... Therefore, a pointer to a function type is *((...) -> ...).
- Solution:
int (*callback)(void) becomes *(() -> int).
Chapter 10: A Comparative Look: <tt>infix</tt> vs. <tt>libffi</tt> and <tt>dyncall</tt>
This chapter provides a practical, code-level comparison of infix with two other popular FFI libraries: libffi (the industry standard) and dyncall. All three are powerful tools, but they are built with different philosophies and trade-offs. We will compare them across three common FFI tasks.
Scenario 1: Calling a Simple Function
Goal: Call a simple function double add_doubles(double a, double b);. This demonstrates the core calling mechanism and API ergonomics.
The <tt>dyncall</tt> Approach
dyncall uses a "call virtual machine" (VM) where arguments are pushed one-by-one. The setup cost is incurred on every call, making it very flexible but less performant for repeated calls to the same function.
#include <dyncall.h>
DCCallVM* vm = dcNewCallVM(4096);
double result;
dcReset(vm);
dcArgDouble(vm, 1.5);
dcArgDouble(vm, 2.5);
result = dcCallDouble(vm, (DCpointer)&add_doubles);
dcFree(vm);
The <tt>libffi</tt> Approach
libffi requires a one-time "Call Interface" (ffi_cif) preparation. Subsequent calls are fast, but the initial type definition is manual and programmatic.
#include <ffi.h>
ffi_cif cif;
ffi_type* args_types[] = { &ffi_type_double, &ffi_type_double };
ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 2,
ret_type, args_types);
double a = 1.5, b = 2.5;
void* args_values[] = { &a, &b };
double result;
ffi_call(&cif, FFI_FN(add_doubles), &result, args_values);
infix_type * ret_type
Definition 901_call_overhead.c:66
The <tt>infix</tt> Approach
infix combines the performance model of libffi (one-time setup) with a much higher-level, human-readable API. The key difference is the use of a simple signature string.
double a = 1.5, b = 2.5;
void*
args[] = { &a, &b };
double result;
Scenario 2: Calling a Function with a Struct
Goal: Call Point move_point(Point p); where Point is {double, double}. This highlights the critical differences in type systems.
The <tt>dyncall</tt> Approach
dyncall requires manual construction of an aggregate object (DCaggr) to describe the struct layout. This must be done at runtime before the call.
#include <dyncall.h>
#include <dyncall_aggregate.h>
typedef struct {
double x, y; }
Point;
DCCallVM* vm = dcNewCallVM(4096);
DCaggr* ag = dcNewAggr(2);
dcAggrField(ag, DC_TYPE_DOUBLE, DC_ALIGNMENT_DOUBLE, 1);
dcAggrField(ag, DC_TYPE_DOUBLE, DC_ALIGNMENT_DOUBLE, 1);
dcCloseAggr(ag);
Point p_in = {10.0, 20.0};
dcReset(vm);
dcArgAggr(vm, ag, &p_in);
dcCallAggr(vm, (DCpointer)&
move_point, ag, &p_out);
dcFreeAggr(ag);
dcFree(vm);
The <tt>libffi</tt> Approach
libffi also requires programmatic struct definition, which is done by creating an ffi_type struct and an array for its elements.
#include <ffi.h>
typedef struct {
double x, y; }
Point;
ffi_type point_elements[] = { &ffi_type_double, &ffi_type_double, NULL };
ffi_type point_type;
point_type.
size = 0; point_type.alignment = 0;
point_type.type = FFI_TYPE_STRUCT;
point_type.elements = point_elements;
ffi_cif cif;
ffi_type* args_types[] = { &point_type };
ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, &point_type, args_types);
Point p_in = {10.0, 20.0};
void* args_values[] = { &p_in };
ffi_call(&cif, FFI_FN(
move_point), &p_out, args_values);
size_t size
Definition infix.h:214
The <tt>infix</tt> Approach
infix handles the entire struct definition within the signature string, making the C code for the FFI call trivial and declarative.
typedef struct {
double x, y; }
Point;
const char* signature = "({double, double}) -> {double, double}";
Point p_in = {10.0, 20.0};
void*
args[] = { &p_in };
Scenario 3: Creating a Callback
Goal: Create a native C function pointer from a custom handler to be used by qsort.
The <tt>dyncall</tt> Approach
dyncallback requires creating a DCCallback object and initializing it with a C function that uses a special dcbArg* API to retrieve arguments one by one.
#include <dyncall_callback.h>
void qsort_handler_dc(DCCallback* cb, DCArgs*
args, DCValue* result,
void* userdata) {
const int* a = (
const int*)dcbArgPointer(
args);
const int* b = (
const int*)dcbArgPointer(
args);
result->i = (*a - *b);
}
DCCallback* cb = dcbNewCallback("pp)i", &qsort_handler_dc, NULL);
qsort(numbers, 5, sizeof(int), (void*)cb);
dcbFree(cb);
The <tt>libffi</tt> Approach
libffi can create a "closure" which is a block of executable memory that acts as the C function pointer. The handler receives arguments via ffi_call-style arrays.
#include <ffi.h>
void qsort_handler_ffi(ffi_cif* cif,
void* ret,
void**
args,
void* userdata) {
const int* a = *(
const int**)
args;
const int* b = *(
const int**)
args;
*(ffi_sarg*)ret = (*a - *b);
}
ffi_cif cif;
ffi_type* args_types[] = { &ffi_type_pointer, &ffi_type_pointer };
ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 2, &ffi_type_sint, args_types);
void* func_ptr = NULL;
ffi_closure* closure = ffi_closure_alloc(sizeof(ffi_closure), &func_ptr);
ffi_prep_closure_loc(closure, &cif, qsort_handler_ffi, NULL, func_ptr);
qsort(numbers, 5, sizeof(int), (void*)func_ptr);
ffi_closure_free(closure);
The <tt>infix</tt> Approach
infix generates a reverse trampoline. The handler is a normal C function that receives its arguments directly, prefixed by the infix_context_t*.
int qsort_handler_infix(const void* a, const void* b) {
return (*(const int*)a - *(const int*)b);
}
typedef int (*compare_func_t)(const void*, const void*);
qsort(numbers, 5, sizeof(int), my_comparator);
void infix_reverse_destroy(infix_reverse_t *)
Destroys a reverse trampoline and frees all associated memory.
Definition trampoline.c:794
Analysis and Takeaways
| Aspect | dyncall | libffi | infix |
| Readability | Low (single-character signatures) | Medium (C code is clear, but type setup is verbose) | High (Human-readable, self-contained signature strings) |
| Performance Model | Setup cost on every call | One-time setup (ffi_prep_cif) | One-time setup (JIT compilation) |
| Type System | Programmatic, with struct support | Manual, programmatic ffi_type creation | Integrated. Types are part of the signature string, with registry support. |
| Ease of Use | Simple for primitives, complex for structs | Complex, powerful, requires deep knowledge of the API | Simple and Declarative, designed for a high-level experience. |
| Callback Handler | Special API (dcbArg*) | Generic void** arguments | Native C arguments (callback) or **Generic void** (closure). |
Chapter 11: Building Language Bindings
The Four Pillars of a Language Binding
A robust language binding built on infix must solve four main challenges:
- Type Mapping & Signature Generation: The binding's primary job is to translate the host language's type representation (e.g., Python's
ctypes.c_int) into an infix signature string.
- Trampoline Caching: The binding must implement a global, persistent cache for trampolines, using the signature string as the key, to amortize the one-time JIT compilation cost.
- Memory & Lifetime Management: The binding must act as a bridge between the host language's Garbage Collector (GC) and C's manual memory management, holding references to objects to prevent premature collection.
- The Callback Bridge: A C handler must be implemented to transfer control from a native C call back into the host language's runtime, handling argument unmarshalling and potential GIL (Global Interpreter Lock) acquisition. This should use the
infix_reverse_create_closure API.
Recipe: Porting a Python Binding from <tt>dyncall</tt> to <tt>infix</tt>
This recipe demonstrates how one might port a Python binding from a library like dyncall to infix.
The dyncall approach involves a "call virtual machine" (DCCallVM*) that arguments are pushed to one-by-one at call time. This is flexible but incurs overhead on every call.
The infix approach shifts the expensive work (parsing and code generation) to a one-time setup phase, making subsequent calls much faster. The core logic of the binding becomes centered around a trampoline cache.
#include <Python.h>
#include <alloca.h>
static PyObject* g_trampoline_cache = NULL;
static PyObject* infix_python_call(PyObject* self, PyObject* py_args) {
PyObject* target_func_capsule = NULL;
const char* signature = NULL;
PyObject* py_func_args = NULL;
if (!PyArg_ParseTuple(py_args, "OsO!", &target_func_capsule, &signature, &PyTuple_Type, &py_func_args)) return NULL;
void* target_func = PyCapsule_GetPointer(target_func_capsule, NULL);
if(!target_func) return NULL;
if (g_trampoline_cache == NULL) g_trampoline_cache = PyDict_New();
PyObject* signature_py = PyUnicode_FromString(signature);
PyObject* capsule = PyDict_GetItem(g_trampoline_cache, signature_py);
if (capsule)
trampoline = (
infix_forward_t*)PyCapsule_GetPointer(capsule,
"infix_trampoline");
else {
PyErr_SetString(PyExc_RuntimeError, "Failed to create infix trampoline.");
Py_DECREF(signature_py);
return NULL;
}
PyDict_SetItem(g_trampoline_cache, signature_py, capsule);
Py_DECREF(capsule);
}
Py_DECREF(signature_py);
size_t num_args = PyTuple_GET_SIZE(py_func_args);
void** c_args = (void**)alloca(sizeof(void*) * num_args);
void* storage = alloca(1024);
char* storage_ptr = (char*)storage;
for (size_t i = 0; i < num_args; ++i) {
PyObject* py_arg = PyTuple_GET_ITEM(py_func_args, i);
if (PyLong_Check(py_arg)) {
long* val = (long*)storage_ptr; *val = PyLong_AsLong(py_arg);
c_args[i] = val; storage_ptr += sizeof(long);
}
else if (PyFloat_Check(py_arg)) {
double* val = (double*)storage_ptr; *val = PyFloat_AsDouble(py_arg);
c_args[i] = val; storage_ptr += sizeof(double);
}
}
cif(target_func, NULL, c_args);
Py_RETURN_NONE;
}