Affix
view release on metacpan or search on metacpan
# SYNOPSIS
```perl
use v5.40;
use Affix qw[:all];
# Bind a function and call it natively.
# Here, we use libm which might be in libm.so, msvcrt.dll, etc.
# C: double pow(double x, double y);
affix libm(), 'pow', [ Double, Double ] => Double;
say pow( 2.0, 10.0 ); # 1024
# Working with C structs is easy
# C: typedef struct { int x; int y; } Point;
# void draw_point(Point p);
typedef Point => Struct[ x => Int, y => Int ];
affix $lib, 'draw_point', [ Point() ] => Void;
draw_point( { x => 10, y => 20 } );
affix $lib, 'get_pos', [] => Point();
my $pt = get_pos();
say sprintf 'x: %d, y: %d', $pt->{x}, $pt->{y};
# We can also allocate and manage raw memory and write data to it
my $ptr = Affix::malloc(1024);
$ptr->[0] = ord('t'); # Direct byte-level access
memcpy( $ptr, 'test', 4 );
# We can also do pointer arithmetic to create new references
my $offset_ptr = Affix::ptr_add( $ptr, 12 );
memcpy( $offset_ptr, 'test', 4 );
A pointer to another type. Affix wraps pointers in magical Perl scalar references that read and write C memory directly
via the `$$` dereference operator.
#### Reading
Dereferencing reads the value from C memory:
```perl
affix $lib, 'get_ptr', [] => Pointer[Int];
my $ptr = get_ptr();
say $$ptr; # Reads the int value from C memory
```
#### Writing
Assigning through the dereference writes directly to C memory:
```
$$ptr = 42; # Writes 42 to the C memory address
```
```perl
# C: int deref_and_add(int* p);
affix $lib, 'deref_and_add', [ Pointer[Int] ] => Int;
my $val = 50;
is deref_and_add( \$val ), 60; # Passes address of $val as int*
# C: void modify_int_ptr(int* p, int new_val);
affix $lib, 'modify_int_ptr', [ Pointer[Int], Int ] => Void;
modify_int_ptr( \$val, 999 );
say $val; # 1000; C function wrote through the pointer
```
#### Array Indexing
Pointers to arrays support direct element access via array subscript syntax. Reads and writes go directly to C memory:
```perl
affix $lib, 'get_array_ptr', [] => Pointer[ Array[ Int, 4 ] ];
my $arr = get_array_ptr();
say $arr->[0]; # Read first element from C memory
$arr->[2] = 99; # Write third element in C memory
```
To take a deep copy (snapshot), dereference into an anonymous array ref:
```perl
my $snapshot = [@$arr];
$snapshot->[0] = 100; # Modifying snapshot does NOT affect C memory
```
$live->{x} = 42; # Updates C memory
```
### Unified Access
Magical `Affix::Pointer` references allow direct field access (`$p->{field}`) without explicit casting.
```perl
affix $lib, 'get_ptr', [] => Pointer[Point];
my $p = get_ptr();
say $p->{x}; # Unified access! Reads directly from C memory.
$p->{y} = 50; # Writes directly to C memory.
```
## Callbacks & Functions
- **`Callback[ [$params] => $ret ]`**: Defines the signature of a C function pointer. Allows you to pass Perl subroutines into C functions.
```perl
# C: void set_handler( void (*cb)(int) );
affix $lib, 'set_handler', [ Callback[ [Int] => Void ] ] => Void;
free($ptr);
```
### `own( $pin )`
Returns true if the given pin is an owned `Affix::Memory` object (i.e., memory allocated via `malloc` or `calloc`
that Perl manages directly). Returns false for unmanaged pins or raw scalars.
```
if (own($ptr)) {
say "Perl owns this memory; it will be freed automatically.";
}
```
### `is_pin( $var )`
Returns true if the variable is currently bound to C memory via `pin`, `cast`, or pointer dereferencing. Returns
false for ordinary Perl scalars.
```perl
my $x = 42;
say is_pin($x); # false
pin $x, libc(), 'errno', Int;
say is_pin($x); # true
```
## Lifecycle & Ownership
### `attach_destructor( $pin, $func_ptr, [$lib] )`
Attaches a custom C function to be called when the Pin is destroyed. This is incredibly useful for C libraries that
require specific cleanup routines (e.g., `SDL_DestroyWindow`, `sqlite3_free`).
```perl
# POINTER UTILITIES
Navigate and inspect raw pointers with helper functions for address arithmetic, null checks, and more.
### `address( $ptr )`
Returns the virtual memory address of the pointer as a Perl Unsigned Integer (`UInt64`). Useful for passing addresses
to other FFI libraries or debugging.
```
say sprintf("Address: 0x%X", address($ptr));
```
### `ptr_add( $ptr, $offset_bytes )`
Returns a new **unmanaged alias Pin** offset by `$offset_bytes`.
```perl
my $int_arr = calloc(10, Int);
my $next_elem = ptr_add($int_arr, sizeof(Int));
```
### `locate_lib( $name, [$version] )`
Searches for a library using Affix's discovery engine and returns its absolute file path as a string. It **does not**
load the library into memory. This is useful if you need to pass the library path to another tool or check for its
existence.
```perl
# Find libssl.so.1.1 or libssl.1.1.dylib
my $path = locate_lib('ssl', '1.1');
say "Found SSL at: $path" if $path;
```
### `find_symbol( $lib_handle, $symbol_name )`
Looks up an exported symbol (function or global variable) inside an already-loaded `Affix::Lib` handle. Returns an
unmanaged `Affix::Pointer` (Pin) of type `Pointer[Void]` pointing to the memory address of the symbol.
```perl
my $lib = load_library('m');
# Get the raw memory address of the 'pow' function
my $pow_ptr = find_symbol($lib, 'pow');
if ($pow_ptr) {
say sprintf("pow() is located at: 0x%X", address($pow_ptr));
}
```
Returns `undef` if the symbol cannot be found.
### `libc()` and `libm()`
Helper functions that locate and return the file paths to the standard C library and the standard math library for the
current platform. Because platform implementations differ wildly (e.g., MSVCRT on Windows, glibc on Linux, libSystem on
macOS), using these helpers guarantees you get the correct library.
Query type sizes, alignments, and field offsets like a compiler would. When working with C APIs, you often need to know
exactly how much memory a structure consumes or where a specific field is located within a block of memory.
### `sizeof( $type )`
Returns the size, in bytes, of any Affix Type object or registered `typedef` name.
```
# C: sizeof(int);
say sizeof( Int ); # 4 (usually)
# C: sizeof(Point);
say sizeof( Point() ); # 8
```
### `alignof( $type )`
Returns the alignment boundary (in bytes) required by the C ABI for the given type.
```perl
say alignof( Int64 ); # 8 (usually)
# Struct alignment is dictated by its largest member
typedef Mixed => Struct[ a => Char, b => Double ];
say alignof( Mixed() ); # 8
```
### `offsetof( $struct_or_union, $field_name )`
Returns the byte offset of a named field within an Aggregate type (Struct or Union). This is incredibly useful for
manual pointer arithmetic.
```perl
typedef Rect => Struct[ x => Int, y => Int, w => Int, h => Int ];
# C: offsetof(Rect, w);
say offsetof( Rect(), 'w' ); # 8 (skips x and y, 4 bytes each)
```
### `types()`
Returns a list of all custom type names currently registered in Affix's global type registry via `typedef`. In scalar
context, returns the total number of registered types.
```perl
my @known_types = types();
say "Registered types: " . join(', ', @known_types);
```
# INTERFACING WITH OTHER LANGUAGES
Guidelines for calling into C++, Rust, Fortran, Go, and Assembly from Affix. Because Affix dynamically loads symbols
according to the C ABI, it can interact with libraries written in almost any language, provided they expose their
functions correctly. Companion modules like [Affix::Build](https://metacpan.org/pod/Affix%3A%3ABuild) make compiling these languages seamless.
Here are the requirements and quirks for interfacing with non-C languages.
This function returns a **dualvar**. It behaves as an integer in numeric context, and magically resolves to the
human-readable system error message (via `strerror` or `FormatMessage`) in string context.
```perl
# Suppose a C file-open function fails
my $fd = c_open("/does/not/exist");
if (!$fd) {
my $err = errno();
# String context
say "Failed to open: $err"; # "No such file or directory"
# Numeric context
if (int($err) == 2) {
say "Code 2 specifically triggered.";
}
}
```
**Note:** You must call `errno()` immediately after the C function invokes, as subsequent Perl operations (like
printing to STDOUT) might overwrite the system's error register.
## Memory Inspection
### `dump( $pin, $length_in_bytes )`
value => 2,
next => {
value => 3,
next => undef # NULL
}
}
};
# Passing to a function that processes the head
affix $lib, 'sum_list', [ Pointer[Node()] ] => Int;
say sum_list($list);
```
## Interacting with C++ Classes (vtable)
```perl
# Manual call to a vtable entry
# Suppose $obj_ptr is a pointer to a C++ object
my $vtable = cast($obj_ptr, Pointer[ Pointer[Void] ]);
my $func_ptr = $vtable->[0]; # Get first method address
builder/Affix/Builder.pm view on Meta::CPAN
require TAP::Harness::Env;
my @fuzz_scripts;
for my $name (@targets) {
my $target = $perl_targets{$name} // do { warn "Unknown Perl fuzz target: $name\n"; $failures++; next };
my $script = $fuzz_dir->child( $target->{script} );
unless ( -f $script ) {
warn "Fuzz script not found: $script\n";
$failures++;
next;
}
say "=" x 60;
say "Fuzzing: $target->{desc}";
say " Script: $target->{script}";
say " Iterations: $iters, Timeout: ${to}s";
say "=" x 60;
push @fuzz_scripts, $script->stringify;
}
if (@fuzz_scripts) {
local $ENV{FUZZ_MAX_ITER} = $iters;
local $ENV{FUZZ_TIMEOUT} = $to;
local $ENV{FUZZ_VERBOSE} = $verbose_fuzz;
my %harness_args
= ( ( verbosity => $verbose ), ( color => -t STDOUT ), lib => [ map { rel2abs( catdir( 'blib', $_ ) ) } qw[arch lib] ], );
my $harness = TAP::Harness::Env->create( \%harness_args );
my $aggr = $harness->runtests( sort @fuzz_scripts );
$failures++ if $aggr->has_errors;
say "";
}
# Smoke test: also quick-build C targets if available
if ( $args{smoke} || $args{all} ) {
my $build_pl = path('infix/build.pl');
if ( -f $build_pl ) {
for my $name ( sort keys %c_targets ) {
say "=" x 60;
say "Building C fuzz target: fuzz:$name";
say " $c_targets{$name}";
say "=" x 60;
my $exit = system( $^X, $build_pl->stringify, "fuzz:$name" );
$failures++ if $exit != 0;
say "";
}
}
else {
say "Skipping C fuzz targets (infix/build.pl not found)";
}
}
say "=" x 60;
if ($failures) {
say "FAILED: $failures target(s) reported crashes or build errors";
}
else {
say "All fuzz targets clean.";
}
return $failures > 0 ? 1 : 0;
}
method get_arguments (@sources) {
$_ = detildefy($_) for grep {defined} $install_base, $destdir, $prefix, values %{$install_paths};
$install_paths = ExtUtils::InstallPaths->new( dist_name => $meta->name );
return;
}
method Build(@args) {
my $method = $self->can( 'step_' . $action );
$method // die "No such action '$action'\n";
exit $method->( $self, @args );
}
method Build_PL() {
die "Pure perl Affix? Ha! You wish.\n" if $pureperl;
say sprintf 'Creating new Build script for %s %s', $meta->name, $meta->version;
$self->write_file( 'Build', sprintf <<'', $^X, __PACKAGE__, __PACKAGE__ );
#!%s
use lib 'builder';
use %s;
my $action = @ARGV && $ARGV[0] =~ /\A\w+\z/ ? shift @ARGV : 'build';
my $opts = {};
while ( @ARGV ) {
my $a = shift @ARGV;
if ( $a =~ /^-(\w+)$/ ) { $opts->{$1} = shift @ARGV // 1; }
elsif ( $a =~ /^-(\w+)=(.+)$/ ) { $opts->{$1} = $2; }
lib/Affix.pod view on Meta::CPAN
=head1 SYNOPSIS
use v5.40;
use Affix qw[:all];
# Bind a function and call it natively.
# Here, we use libm which might be in libm.so, msvcrt.dll, etc.
# C: double pow(double x, double y);
affix libm(), 'pow', [ Double, Double ] => Double;
say pow( 2.0, 10.0 ); # 1024
# Working with C structs is easy
# C: typedef struct { int x; int y; } Point;
# void draw_point(Point p);
typedef Point => Struct[ x => Int, y => Int ];
affix $lib, 'draw_point', [ Point() ] => Void;
draw_point( { x => 10, y => 20 } );
affix $lib, 'get_pos', [] => Point();
my $pt = get_pos();
say sprintf 'x: %d, y: %d', $pt->{x}, $pt->{y};
# We can also allocate and manage raw memory and write data to it
my $ptr = Affix::malloc(1024);
$ptr->[0] = ord('t'); # Direct byte-level access
memcpy( $ptr, 'test', 4 );
# We can also do pointer arithmetic to create new references
my $offset_ptr = Affix::ptr_add( $ptr, 12 );
memcpy( $offset_ptr, 'test', 4 );
lib/Affix.pod view on Meta::CPAN
A pointer to another type. Affix wraps pointers in magical Perl scalar references that read and write C memory directly
via the C<$$> dereference operator.
=head4 Reading
Dereferencing reads the value from C memory:
affix $lib, 'get_ptr', [] => Pointer[Int];
my $ptr = get_ptr();
say $$ptr; # Reads the int value from C memory
=head4 Writing
Assigning through the dereference writes directly to C memory:
$$ptr = 42; # Writes 42 to the C memory address
This works for deep pointer chains as well:
# int*** ptr; ***ptr = 5;
lib/Affix.pod view on Meta::CPAN
# C: int deref_and_add(int* p);
affix $lib, 'deref_and_add', [ Pointer[Int] ] => Int;
my $val = 50;
is deref_and_add( \$val ), 60; # Passes address of $val as int*
# C: void modify_int_ptr(int* p, int new_val);
affix $lib, 'modify_int_ptr', [ Pointer[Int], Int ] => Void;
modify_int_ptr( \$val, 999 );
say $val; # 1000; C function wrote through the pointer
=head4 Array Indexing
Pointers to arrays support direct element access via array subscript syntax. Reads and writes go directly to C memory:
affix $lib, 'get_array_ptr', [] => Pointer[ Array[ Int, 4 ] ];
my $arr = get_array_ptr();
say $arr->[0]; # Read first element from C memory
$arr->[2] = 99; # Write third element in C memory
To take a deep copy (snapshot), dereference into an anonymous array ref:
my $snapshot = [@$arr];
$snapshot->[0] = 100; # Modifying snapshot does NOT affect C memory
=head4 Void Pointers
If C<$type> is C<Void>, the pointer is "terminal." Dereferencing it will return C<undef>. In this case, use C<cast()>
lib/Affix.pod view on Meta::CPAN
# Example: Live view of a struct
my $live = cast( $ptr, Struct[ x => Int, y => Int ] );
$live->{x} = 42; # Updates C memory
=head3 Unified Access
Magical C<Affix::Pointer> references allow direct field access (C<< $p->{field} >>) without explicit casting.
affix $lib, 'get_ptr', [] => Pointer[Point];
my $p = get_ptr();
say $p->{x}; # Unified access! Reads directly from C memory.
$p->{y} = 50; # Writes directly to C memory.
=head2 Callbacks & Functions
=over
=item * B<C<< Callback[ [$params] => $ret ] >>>: Defines the signature of a C function pointer. Allows you to pass Perl subroutines into C functions.
# C: void set_handler( void (*cb)(int) );
affix $lib, 'set_handler', [ Callback[ [Int] => Void ] ] => Void;
lib/Affix.pod view on Meta::CPAN
a segmentation fault.
free($ptr);
=head3 C<own( $pin )>
Returns true if the given pin is an owned C<Affix::Memory> object (i.e., memory allocated via C<malloc> or C<calloc>
that Perl manages directly). Returns false for unmanaged pins or raw scalars.
if (own($ptr)) {
say "Perl owns this memory; it will be freed automatically.";
}
=head3 C<is_pin( $var )>
Returns true if the variable is currently bound to C memory via C<pin>, C<cast>, or pointer dereferencing. Returns
false for ordinary Perl scalars.
my $x = 42;
say is_pin($x); # false
pin $x, libc(), 'errno', Int;
say is_pin($x); # true
=head2 Lifecycle & Ownership
=head3 C<attach_destructor( $pin, $func_ptr, [$lib] )>
Attaches a custom C function to be called when the Pin is destroyed. This is incredibly useful for C libraries that
require specific cleanup routines (e.g., C<SDL_DestroyWindow>, C<sqlite3_free>).
# Find the address of the library's custom free function
my $free_func = find_symbol($my_lib, 'custom_free');
lib/Affix.pod view on Meta::CPAN
=head1 POINTER UTILITIES
Navigate and inspect raw pointers with helper functions for address arithmetic, null checks, and more.
=head3 C<address( $ptr )>
Returns the virtual memory address of the pointer as a Perl Unsigned Integer (C<UInt64>). Useful for passing addresses
to other FFI libraries or debugging.
say sprintf("Address: 0x%X", address($ptr));
=head3 C<ptr_add( $ptr, $offset_bytes )>
Returns a new B<unmanaged alias Pin> offset by C<$offset_bytes>.
my $int_arr = calloc(10, Int);
my $next_elem = ptr_add($int_arr, sizeof(Int));
I<Note: If C<$ptr> is an Array type, C<ptr_add> correctly decays the returned pin into a Pointer to the element type.>
lib/Affix.pod view on Meta::CPAN
will search the currently running executable process.
=head3 C<locate_lib( $name, [$version] )>
Searches for a library using Affix's discovery engine and returns its absolute file path as a string. It B<does not>
load the library into memory. This is useful if you need to pass the library path to another tool or check for its
existence.
# Find libssl.so.1.1 or libssl.1.1.dylib
my $path = locate_lib('ssl', '1.1');
say "Found SSL at: $path" if $path;
=head3 C<find_symbol( $lib_handle, $symbol_name )>
Looks up an exported symbol (function or global variable) inside an already-loaded C<Affix::Lib> handle. Returns an
unmanaged C<Affix::Pointer> (Pin) of type C<Pointer[Void]> pointing to the memory address of the symbol.
my $lib = load_library('m');
# Get the raw memory address of the 'pow' function
my $pow_ptr = find_symbol($lib, 'pow');
if ($pow_ptr) {
say sprintf("pow() is located at: 0x%X", address($pow_ptr));
}
Returns C<undef> if the symbol cannot be found.
=head3 C<libc()> and C<libm()>
Helper functions that locate and return the file paths to the standard C library and the standard math library for the
current platform. Because platform implementations differ wildly (e.g., MSVCRT on Windows, glibc on Linux, libSystem on
macOS), using these helpers guarantees you get the correct library.
lib/Affix.pod view on Meta::CPAN
=head1 INTROSPECTION
Query type sizes, alignments, and field offsets like a compiler would. When working with C APIs, you often need to know
exactly how much memory a structure consumes or where a specific field is located within a block of memory.
=head3 C<sizeof( $type )>
Returns the size, in bytes, of any Affix Type object or registered C<typedef> name.
# C: sizeof(int);
say sizeof( Int ); # 4 (usually)
# C: sizeof(Point);
say sizeof( Point() ); # 8
=head3 C<alignof( $type )>
Returns the alignment boundary (in bytes) required by the C ABI for the given type.
say alignof( Int64 ); # 8 (usually)
# Struct alignment is dictated by its largest member
typedef Mixed => Struct[ a => Char, b => Double ];
say alignof( Mixed() ); # 8
=head3 C<offsetof( $struct_or_union, $field_name )>
Returns the byte offset of a named field within an Aggregate type (Struct or Union). This is incredibly useful for
manual pointer arithmetic.
typedef Rect => Struct[ x => Int, y => Int, w => Int, h => Int ];
# C: offsetof(Rect, w);
say offsetof( Rect(), 'w' ); # 8 (skips x and y, 4 bytes each)
=head3 C<types()>
Returns a list of all custom type names currently registered in Affix's global type registry via C<typedef>. In scalar
context, returns the total number of registered types.
my @known_types = types();
say "Registered types: " . join(', ', @known_types);
=head1 INTERFACING WITH OTHER LANGUAGES
Guidelines for calling into C++, Rust, Fortran, Go, and Assembly from Affix. Because Affix dynamically loads symbols
according to the C ABI, it can interact with libraries written in almost any language, provided they expose their
functions correctly. Companion modules like L<Affix::Build> make compiling these languages seamless.
Here are the requirements and quirks for interfacing with non-C languages.
=head2 C++
lib/Affix.pod view on Meta::CPAN
This function returns a B<dualvar>. It behaves as an integer in numeric context, and magically resolves to the
human-readable system error message (via C<strerror> or C<FormatMessage>) in string context.
# Suppose a C file-open function fails
my $fd = c_open("/does/not/exist");
if (!$fd) {
my $err = errno();
# String context
say "Failed to open: $err"; # "No such file or directory"
# Numeric context
if (int($err) == 2) {
say "Code 2 specifically triggered.";
}
}
B<Note:> You must call C<errno()> immediately after the C function invokes, as subsequent Perl operations (like
printing to STDOUT) might overwrite the system's error register.
=head2 Memory Inspection
=head3 C<dump( $pin, $length_in_bytes )>
lib/Affix.pod view on Meta::CPAN
value => 2,
next => {
value => 3,
next => undef # NULL
}
}
};
# Passing to a function that processes the head
affix $lib, 'sum_list', [ Pointer[Node()] ] => Int;
say sum_list($list);
=head2 Interacting with C++ Classes (vtable)
# Manual call to a vtable entry
# Suppose $obj_ptr is a pointer to a C++ object
my $vtable = cast($obj_ptr, Pointer[ Pointer[Void] ]);
my $func_ptr = $vtable->[0]; # Get first method address
# Bind and call
my $method = wrap undef, $func_ptr, [Pointer[Void], Int] => Void;
( run in 1.264 second using v1.01-cache-2.11-cpan-a49fcb8fa48 )