Some C functions don't have a fixed number of arguments. The most famous example is printf, which takes a format string and... almost everything else. Affix supports variadic functions, but they require a little more care than standard functions because the C compiler isn't there to cast types for you.
The Recipe
We will wrap the standard C library's snprintf. This is safer than printf because it writes to a buffer.
We also handle a common cross-platform annoyance: on Windows, this function is often named _snprintf, while on Linux/macOS it is snprintf. We can normalize this using symbol aliasing.
use v5.40;
use Affix;
# 1. Bind snprintf
# Windows uses _snprintf, POSIX uses snprintf.
# We detect the OS and bind the correct symbol to the name 'snprintf'.
my $symbol = ( $^O eq 'MSWin32' ? '_' : '' ) . 'snprintf';
# Signature: int snprintf(char *str, size_t size, const char *format, ...);
# We use '...' (or VarArgs) to indicate the variadic part.
affix libc, [ $symbol => 'snprintf' ], [ Pointer [Char], Int, String, VarArgs ] => Int;
# 2. Prepare a buffer
# We need a place for C to write the formatted string.
my $size = 100;
my $buffer = "\0" x $size;
# 3. Call with Inference
# Affix guesses the C type based on the Perl value.
# "A String" -> char*
# 123 -> int64_t
# 12.34 -> double
snprintf( $buffer, $size, 'Name: %s, ID: %d, Score: %.2f', 'Alice', 42, 99.9 );
# Clean up the null terminator and print
$buffer =~ s/\0.*//;
say $buffer; # "Name: Alice, ID: 42, Score: 99.90"
# 4. Explicit Coercion
# Sometimes inference isn't enough.
# Here we want to print a pointer address (%p).
# Passing a Perl integer might be interpreted as a value, not a pointer.
# We use coerce() to force specific types.
my $ptr = 0xDEADBEEF;
snprintf( $buffer, $size, 'Pointer address: %p', coerce( Pointer [Void], $ptr ) );
say $buffer =~ s/\0.*//r;
How It Works
-
1. Symbol aliasing
affix $lib, [ 'RealSymbol', 'PerlName' ], ...Often, C libraries have messy naming conventions or platform-specific quirks (like
_snprintfvssnprintf), or the library has mangled symbol names. By passing an array reference as the symbol name, you tell Affix: "Find RealSymbol in the library, but create a Perl subroutine named PerlName." -
2. VarArgs in signature
[ ..., VarArgs ]The
VarArgsconstant (or the string';') tells Affix: "Stop checking types here. Everything after this point is dynamic." -
3. Dynamic JIT
When you call a variadic function, Affix looks at the arguments you provided at that moment. It generates a custom, temporary infix style signature on the fly (e.g.,
(*char, size_t, *char, *char, int64, double)->int) and compiles a trampoline for it.These trampolines are cached, so calling
snprintfrepeatedly with the same argument types is fast. -
4. Coercion
coerce( Type, $value )If you need to pass a
float(not double), ashort, or astructby value, the default inference won't work.coerceattaches a "Type Hint" to the value. Affix sees this hint and uses the exact type you requested.
Kitchen Reminders
-
Format strings
Affix does not validate your format string (e.g.
"%s"). If you pass an integer to a%splaceholder,snprintfwill assume it's a pointer address and try to read memory from it which will likely result in a segfault.