NAME

Affix - A Foreign Function Interface eXtension

SYNOPSIS

use v5.40;
use Affix;

# Load a Library
my $lib = load_library(libm);    # libm.so / msvcrt.dll

# Bind a Function
#    double pow(double x, double y);
affix $lib, 'pow', [ Double, Double ] => Double;

# Call it
say pow( 2.0, 10.0 );    # 1024

# Allocate 1KiB of raw memory
my $ptr = Affix::malloc(1024);

# Write raw data to the pointer
Affix::memcpy( $ptr, 'test', 4 );

# Poiner arithmetic creates a new reference (doesn't modify original)
my $offset_ptr = Affix::ptr_add( $ptr, 12 );
Affix::memcpy( $offset_ptr, 'test', 4 );

# Inspect memory with a hex dump to STDOUT
Affix::dump( $ptr, 32 );

# Release the memory
Affix::free($ptr);

DESCRIPTION

Affix is a high-performance Foreign Function Interface (FFI) for Perl. It allows you to load dynamic libraries (DLLs, shared objects) and call their functions natively without writing XS code or configuring a C compiler.

It distinguishes itself from other FFI solutions by using infix, a custom lightweight JIT engine. When you bind a function, Affix generates machine code at runtime (a 'trampoline') to handle the argument marshalling. This results in significantly lower overhead than generic FFI wrappers that rely on dynamic dispatch per-call.

EXPORTS

Affix exports standard types (Int, Double, etc.) and core functions (affix, wrap, load_library) by default.

You can control imports using tags:

use Affix qw[:all];    # Import everything
use Affix qw[:lib];    # Library helpers (libc, libm, load_library...)
use Affix qw[:memory]; # malloc, free, memcpy, cast, dump...
use Affix qw[:pin];    # Variable binding (pin, unpin)
use Affix qw[:types];  # Types only (Int, Struct, Pointer...)

Core API

Affix's API is designed to be expressive. Let's start at the beginning with the eponymous affix( ... ) function.

affix( ... )

Attaches a symbol from a library to a named perl subroutine in the current namespace.

# Standard: Load from library
affix $lib, 'pow', [ Double, Double ] => Double;

# Rename: Load 'pow', install as 'power'
affix $lib, [ pow => 'power' ], [ Double, Double ] => Double;

# Raw pointer: Bind a specific memory address (vtables, JIT, dlsym, etc.)
affix undef, [ $ptr => 'my_func' ], [Int] => Void;

Parameters:

On success, affix( ... ) installs the subroutine and returns the generated code reference.

wrap( ... )

Creates a wrapper around a given symbol and returns it as an anonymous CODE reference.

# From library
my $pow = wrap $lib, 'pow', [ Double, Double ] => Double;

# Call the function
my $result = $pow->( 2, 5 );

# From a raw pointer
# Note: Library argument is undef
my $func = wrap undef, $ptr, [Int] => Void;

Arguments are nearly identical to [affix( ... )](#affix) except you cannot provide an alias for the function name.

pin( ... )

my $scalar;
# Bind $scalar to the global integer variable 'errno' in libc
pin $scalar, libc(), 'errno', Int;
$scalar = 0;   # Writes to C memory
sysopen( ... );
say $scalar;   # Reads from C memory

Variables exported by a library (global/extern variables) can be accessed using pin. Reading the scalar reads the memory; writing to it writes to the memory.

Parameters:

unpin( ... )

unpin $errno;

Removes the magic applied by pin( ... ) to a variable. The variable retains its last value but is no longer linked to C memory.

typedef( ... )

typedef MyType => Struct[ name => String, age => Int ];


# Now use it in signatures
affix $lib, 'func', [ MyType() ] => Void;

Registers a named type alias. This is required for:

coerce( $type, $value )

Used primarily with Variadic Functions. It wraps a value with type information so Affix knows how to marshal it when no compile-time signature is available.

# Hint that we are passing a Float, not a Double
coerce( Float, 1.5 );

get_last_error_message( )

Returns a string describing the most recent error that occurred during library loading or symbol lookup.

Library Utilities

Locating libraries on different platforms can be tricky. These utilities help you load and manage dynamic libraries.

They are exported by default but may be imported specifically with the :lib tag.

load_library( $path )

my $lib = load_library( 'user32.dll' );

Locates and loads a dynamic library, returning an opaque (Affix::Lib) handle.

If you pass a name without an extension (e.g., 'm'), Affix applies platform-specific prefixes/suffixes (e.g., 'libm.so', 'libm.dylib', 'm.dll') and searches standard system paths.

locate_lib( $name, [$version] )

my $path = locate_lib('ssl', '1.1');

Searches system paths (LD_LIBRARY_PATH, PATH, DYLD_LIBRARY_PATH, etc.) and returns the full absolute path to the library file without loading it.

find_symbol( $lib, $name )

Returns the raw memory address (as an integer) of a symbol. Useful if you need to pass a function pointer value to C, rather than calling it.

libc() / libm()

Helpers returning handles to the standard C library and math library.

Memory Management

Affix uses pins to manage raw memory. A pin is a magical scalar reference holding a memory address and type information.

malloc( $size )

my $ptr = malloc( 1024 );

Allocates $size bytes of uninitialized memory. Returns a Pointer[Void] pin. Memory allocated this way is managed by Perl (freed automatically when the pin goes out of scope).

calloc( $num, $size_or_type )

my $array = calloc( 10, Int );

Allocates memory for an array of $count elements and initializes them to zero. You may pass a Type object (like Int) or a raw size in bytes.

realloc( $ptr, $new_size )

$ptr = realloc( $ptr, $new_size );

Resizes the memory pointed to by $ptr. Returns the new pointer (the original pin is updated automatically).

free( $ptr )

free( $ptr );

Releases memory allocated by Affix.

Note: Only use this on memory allocated by malloc, calloc, or strdup. Do not attempt to free pointers returned by C libraries unless the library documentation explicitly says you own that memory.

cast( $ptr, $type )

my $void  = malloc(4);
my $int   = cast( $void, Int ); # Read immediate value
my $int_p = cast( $void, Pointer[Int] ); # Return new Pin

Reinterprets a pointer.

own( $ptr, [$bool] )

own( $ptr, $bool );

Controls lifecycle management of the pointer.

Double free warning: If you call own($p, 1) and then pass $p to a C function that also frees that memory, your program will crash. Only take ownership if you are sure the C library expects you to free the memory.

address( $ptr )

Returns the virtual memory address of the pointer as a UInt64.

Pointer Arithmetic & Utilities

Raw Memory Operatoins

Standard C memory operations are available for high-performance manipulation of pins.

dump( $ptr, $length )

Prints a hex dump of the memory at $ptr to STDOUT.

Introspection

sizeof( $type )

my $size = sizeof( Int );
my $size_rect = sizeof( Struct[ x => Int, y => Int ] );

Returns the size, in bytes, of a Type object.

offsetof( $struct_type, $field_name )

my $struct = Struct[ name => String, age => Int ];
my $offset = offsetof( $struct, 'age' );

Returns the byte offset of a field within a Struct or Union.

alignof( $type )

Returns the required alignment bytes for a Type.

types()

Returns a list of all named types currently registered in the system.

Types

Affix signatures are built using these helper functions

# Example Signature
[ Int, String ] => Void

Primitive Types

Primitives map to native C types.

TypeDescription
VoidReturns nothing
BoolMapped to Perl true/false
Charsigned char (8-bit usually)
UCharunsigned char
SCharExplicitly signed char
Shortsigned short
UShortunsigned short
Intsigned int (platform native, usually 32-bit)
UIntunsigned int
Longsigned long (32-bit on Win64, 64-bit on Linux64)
ULongunsigned long
LongLongsigned long long (guaranteed 64-bit)
ULongLongunsigned long long
Float16Half-precision (16-bit) float
Float32-bit float
Double64-bit float
LongDoublePlatform specific (80-bit or 128-bit)
Size_tsize_t
SSize_tssize_t

Explicit Width Types

For precise control, use these types which are guaranteed to have specific bit widths across all platforms:

Int8, UInt8
Int16, UInt16
Int32, UInt32
Int64, UInt64
Int128, UInt128 (Passed as Decimal Strings)
Float16 (Half-precision float)

128-bit integers, if supported by the compiler, must be passed as strings to/from Perl.

Pointers

Pointers are the glue of C. Affix provides distinct ways to handle them based on intent.

Pins (Managed Pointers)

For manual memory management, use malloc, calloc, or cast. These return Pins. A Pin is a reference to a scalar holding the memory address, blessed with magic.

my $ptr = malloc(1024);   # Allocate 1024 bytes
my $view = cast($ptr, Int); # Treat it as an integer

$$view = 123;             # Write 123 to the memory
free($ptr);               # Free it manually (optional, GC handles it otherwise)

Special Types

Aggregates

Structs

Structs are the bread and butter of C APIs. In Affix, they map to Perl Hash References.

# C: typedef struct { int x; int y; } Point;
#    void draw_line(Point a, Point b);

# Define the type (recommended for reuse)
typedef Point => Struct [
    x => Int,
    y => Int
];

# Bind the function
affix $lib, 'draw_line', [ Point, Point ] => Void;

# Call with HashRefs
draw_line( { x => 0, y => 0 }, { x => 100, y => 100 } );

Nested Structs: Affix handles deep structures automatically.

typedef Rect => Struct [
    top_left     => Point,
    bottom_right => Point,
    color        => Int
];

draw_rect({
    top_left     => { x => 10, y => 10 },
    bottom_right => { x => 50, y => 50 },
    color        => 0xFF0000
});

Bitfields

Affix supports C-style bitfields using a natural pipe (|) syntax. This allows you to specify the number of bits for a member.

# C: typedef struct {
#        uint32_t enabled : 1;
#        uint32_t mode    : 3;
#        uint32_t value   : 12;
#    } Config;

typedef Config => Struct [
    enabled => UInt32 | 1,
    mode    => UInt32 | 3,
    value   => UInt32 | 12
];

When marshalling to C, Affix automatically handles bitmasking and shifting to ensure that only the specified bits are modified, preserving other fields in the same memory slot. When marshalling back to Perl, the values are automatically extracted and returned as standard integers.

Unions

Maps to Perl hash references with a single key.

# C: union Event { int key_code; float pressure; };
typedef Event => Union [
    key_code => Int,
    pressure => Float
];

# Pass an integer
handle_event( { key_code => 27 } );

# Pass a float
handle_event( { pressure => 0.5 } );

Working with Arrays

SIMD Vectors

Vectors (e.g. __m128, __m256, __m512 on x86; float32x4_t on ARM) are first-class types in Affix. You can interact with them in two ways:

# C: typedef float v4f __attribute__((vector_size(16)));
#    v4f add_vecs(v4f a, v4f b);
affix $lib, 'add_vecs', [ Vector[4, Float], Vector[4, Float] ] => Vector[4, Float];

Affix provides several convenience aliases for common SIMD types:

# Option 1: Array References (Convenient)
my $res = add_vecs( [1, 2, 3, 4], [10, 20, 30, 40] );
# $res is [11.0, 22.0, 33.0, 44.0]

# Option 2: Packed Strings (Fast)
# Useful for tight loops, graphics, or physics math
my $packed_a = pack('f4', 1.0, 2.0, 3.0, 4.0);
my $packed_b = pack('f4', 10.0, 20.0, 30.0, 40.0);

# Pass binary strings directly
my $res_ref = add_vecs( $packed_a, $packed_b );

Enumerations

typedef Status => Enum [
    [ OK => 0 ],
    'ERROR',                    # Auto-increments to 1
    [ FLAG_A => 1 << 0 ],       # Bit shifting
    [ FLAG_B => '1 << 1' ],     # String expression
    [ FLAG_C => 'FLAG_B << 1' ] # References previous keys
];

Defines a C enum backed by an integer.

Variadic Functions (VarArgs)

Affix supports C functions that take a variable number of arguments, like printf.

# C: int printf(const char* format, ...);
affix libc, 'printf', [ String, VarArgs ] => Int;

When calling a variadic function, Affix performs dynamic type inference at runtime for the extra arguments:

printf("Hello %s, count is %d\n", "World", 123);

Hinting Types with coerce()

Sometimes standard inference isn't enough (e.g., passing a float instead of double, or passing a Struct by value). Use coerce($type, $value) to explicitly hint the type.

# Passing a struct by value to a variadic function
typedef Point => Struct [ x=>Int, y=>Int ];
my $p = { x => 10, y => 20 };

# Without coerce(), $p would likely be treated as an error or generic pointer
my_variadic_func( "Point: %P", coerce( Point(), $p ) );

Callbacks

You can pass Perl subroutines to C functions that expect function pointers.

# C: void set_handler( void (*callback)(int status, const char* msg) );

affix $lib, 'set_handler',
    [ Callback[ [Int, String] => Void ] ] => Void;

set_handler(sub ($status, $msg) {
    say "Received status $status: $msg";
});

Note: The callback is valid only as long as the C function holds onto it. If the C library stores the function pointer globally, ensure your Perl code keeps the reference alive if necessary (though Affix handles the trampoline lifecycle automatically for the duration of the call).

Usage Examples

Full examples can be found in the eg/ directory, Affix based modules are on CPAN (SDL3) but here are a few to get you started.

Working with Complex Types

Defining a recursive data structure like a linked list:

use Affix qw[:all];

# Forward declare the named type
typedef 'Node';

# Define the struct using the named reference
typedef Node => Struct [
    value => Int,
    next  => Pointer[ Node() ]
];

# Use it in a function signature
affix $lib, 'process_list', [ Pointer[ Node() ] ] => Int;

# Create and pass a linked list from Perl
my $list = {
    value => 1,
    next  => {
        value => 2,
        next  => {
            value => 3,
            next  => undef # NULL pointer
        }
    }
};

my $sum = process_list($list);

Wrapping vs Affixing

Use affix when you want to install a function directly into your current package:

package MyLib;
use Affix qw[:all];
affix 'libc', 'puts', [String] => Int;

# Called as a normal sub
puts("Hello world");

Use wrap when you want a scoped, anonymous code reference:

use Affix qw[:all];
my $puts = wrap 'libc', 'puts', [String] => Int;

# Called via the reference
$puts->("Hello world");

Utilities

errno()

my $err = errno();
die "Error $err: " . int($err);

Access the errno (Linux/Unix) or GetLastError (Windows) from the most recent FFI call. This must be called immediately after the function invokes to ensure accuracy.

The return value is a dualvar:

sv_dump( $sv )

Dumps the internal flags and structure of a Perl SV.

Thread Safety & Concurrency

Affix bridges Perl (a single-threaded interpreter, generally) with libraries that may be multi-threaded. This creates potential hazards that you must manage.

1. Initialization Phase vs. Execution Phase

Functions that modify Affix's global state are not thread-safe. You must perform all definitions in the main thread before starting any background threads or loops in the library.

Unsafe operations that you should never call from Callbacks or in a threaded context:

2. Callbacks

When passing a Perl subroutine as a Callback, avoid performing complex Perl operations like loading modules or defining subs inside callback triggered on a foreign thread. Such callbacks should remain simple: process data, update a shared variable, and return.

If the library executes the callback from a background thread (e.g., window managers, audio callbacks), Affix attempts to attach a temporary Perl context to that thread. This should be sufficient but Perl is gonna be Perl.

EXAMPLES

See The Affix Cookbook for comprehensive guides to using Affix.

SEE ALSO

FFI::Platypus, C::DynaLib, XS::TCC, C::Blocks

All the heavy lifting is done by infix, my JIT compiler and type introspection engine.

AUTHOR

Sanko Robinson sanko@cpan.org

COPYRIGHT

Copyright (C) 2023-2026 by Sanko Robinson.

This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.