Affix

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

# 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 );

README.md  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 `$$` 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
```

README.md  view on Meta::CPAN

```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
```

README.md  view on Meta::CPAN

$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;

README.md  view on Meta::CPAN

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

README.md  view on Meta::CPAN

# 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));
```

README.md  view on Meta::CPAN


### `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.

README.md  view on Meta::CPAN


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.

README.md  view on Meta::CPAN

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 )`

README.md  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);
```

## 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 )