C libraries often use callback functions to delegate logic back to the user. The standard library's qsort is the classic example: it knows how to sort, but it doesn't know how you'd like to compare your data. Affix allows you to pass a standard Perl subroutine (CodeRef) where C expects a function pointer.
The Recipe
Let's sort a list of integers using qsort.
use v5.40;
use Affix;
# 1. Bind qsort
# void qsort(void *base, size_t nmemb, size_t size,
# int (*compar)(const void *, const void *));
#
# The callback signature is defined inside the argument list.
# Callback[ [Args...] => ReturnType ]
affix libc, 'qsort', [
Pointer [Int], Size_t, Size_t,
Callback [ [ Pointer [Int], Pointer [Int] ] => Int ]
] => Void;
# 2. Prepare Data
# qsort works on a raw memory array.
# We create a C array of integers.
my @nums = ( 88, 56, 100, 2, 25 );
my $count = scalar @nums;
# 3. Define the Comparator
# C passes us pointers to the two items being compared.
# We must dereference them to get the values.
my $compare_fn = sub ( $p_a, $p_b ) { $$p_a <=> $$p_b };
# 4. Call
# We pass the ArrayRef directly. Affix handles the pointer decay
# and write-back (see Chapter 10).
# sizeof(int) is usually 4.
qsort( \@nums, $count, 4, $compare_fn );
say join ', ', @nums; # 2, 25, 56, 88, 100
How It Works
-
1. The
CallbacktypeCallback[ [Pointer[Int], Pointer[Int]] => Int ]This tells Affix to create a reverse trampoline. It generates a small piece of C code that looks like a standard C function. When that C code is called by
qsort, it:- Marshals the C arguments (two pointers here) into Perl variables (
SV*). - Calls your Perl subroutine
$compare_fn. - Takes the integer return value and passes it back to C.
- Marshals the C arguments (two pointers here) into Perl variables (
-
2. Pointer dereferencing
$$p_a <=> $$p_b;In the callback signature, we claimed the arguments were
Pointer[Int]. Affix receives the raw address from C, wraps it in a pin with the typeInt, and passes it to your sub. Dereferencing it reads the integer value from memory. In Affix, pointers to primitives (likeint *) arrive in your callback as standard Perl scalar references. However, these references point to "Magical Scalars." When you use$$p_a, Perl triggers Affix's internal VTable, which reads the integer directly from the C memory address. This allows you to use idiomatic Perl syntax while operating on raw C data.If we decided to keep
qsortmore generic, we could have writtenaffix libc, 'qsort', [ Pointer [Int], Size_t, Size_t, Callback [ [ Pointer [Void], Pointer [Void] ] => Int ] ] => Void;and written our callback like this:my $compare_fn = sub ( $p_a, $p_b ) { cast( $p_a, Int ) <=> cast( $p_b, Int ) };
Kitchen Reminders
-
Scope and lifecycle
The magic trampoline created for your subroutine exists only as long as the C call is running. If you pass a callback to a C function that stores it for later use (like setting an event handler in a GUI library), you must ensure your Perl CodeRef stays alive.
-
Exceptions
If your Perl callback throws an exception (
croak,die, etc.), Affix catches it, issues a warning, and returns a zero/void value to C to prevent crashing the host application. C does not understand Perl exceptions and the C function might not understand this either. Be careful!