Affix

 view release on metacpan or  search on metacpan

lib/Affix.pod  view on Meta::CPAN

as an I<FFI Escape Hatch> (similar to C<const_cast> in C++).

    my $point = cast($addr, Struct[ x => Int, y => Int ]);

    readonly($point, 1); # Lock the entire struct
    $point->{x} = 10;    # FATAL ERROR

=head2 Recursive Protection

When an aggregate (Struct or Array) is marked as read-only, Affix automatically propagates that protection to all of
its members.

    my $rect = cast($addr, Const[Struct[top => Struct[ x => Int, y => Int ], bottom => Struct[ x => Int, y => Int ] ]]);

    # Even though 'x' wasn't explicitly marked Const, it inherited protection
    # from the parent struct.
    $rect->{top}{x} = 5; # FATAL ERROR

=head2 Casting with Const

When using C<cast( ... )>, you can prepend a C<+> to the type signature to create an immutable view of a raw memory
address.

    my $view = cast($raw_addr, Const[MyStruct]);
    # $view is now a read-only HashRef mapping to C memory.

=head1 Zero-copy Aggregates

Structs, unions, and arrays map directly to C memory—no deep copies required. When C returns a pointer to an
aggregate, Affix wraps it in a magical Perl reference that reads and writes C memory in real time.

=head3 Native Array Indexing

C Arrays are traversed using standard Perl array syntax.

    typedef Task => Struct[ id => Int, name => String ];
    affix $lib, 'get_tasks', [] => Pointer[ Array[ Task(), 10 ] ];

    my $tasks = get_tasks();
    $tasks->[5]{id} = 404; # Writes directly to C memory!

=head3 Deep Null Safety

Traversing a `NULL` pointer in C causes a segfault. Affix wraps C memory in Perl safety rails. If you try to traverse a
`NULL` pointer inside a struct, Affix intercepts it and throws a standard Perl exception (C<Can't use an undefined
value as a HASH reference>).

=head1 LIBRARIES & SYMBOLS

Load and inspect dynamic libraries across platforms. Affix's smart discovery engine handles varying extensions,
prefixes, and search paths automatically.

=head2 Library Discovery

When you provide a bare library name (e.g., C<'z'>, C<'ssl'>, C<'user32'>) rather than an absolute path, Affix
automatically formats the name for the current platform (e.g., C<libz.so>, C<libz.dylib>, C<z.dll>) and searches the
following locations in order:

=over

=item 1. B<Standard System Paths:> Windows C<System32>/C<SysWOW64>; Unix C</usr/local/lib>, C</usr/lib>, C</lib>, C</usr/lib/system>.

=item 2. B<Environment Variables:> Paths defined in C<LD_LIBRARY_PATH>, C<DYLD_LIBRARY_PATH>, C<DYLD_FALLBACK_LIBRARY_PATH>, or C<PATH>.

=item 3. B<Local Paths:> The current working directory (C<.>) and its C<lib/> subdirectory.

=back

=head2 Functions

=head3 C<load_library( $path_or_name )>

Locates and loads a dynamic library into memory, returning an opaque C<Affix::Lib> handle.

    my $lib = load_library('sqlite3');

B<Lifecycle:> Library handles are thread-safe and internally reference-counted. The underlying OS library is only
closed (e.g., via C<dlclose> or C<FreeLibrary>) when all Affix wrappers and pins relying on it are destroyed.

I<Note:> When using C<affix()> or C<wrap()>, you can safely pass the string name directly (e.g., C<affix('sqlite3',
...)>) and Affix will call C<load_library> for you internally. If you pass C<undef> instead of a library name, Affix
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.

    # Bind 'puts' from the standard C library
    affix libc(), 'puts', [String] => Int;

    # Bind 'cos' from the math library
    affix libm(), 'cos', [Double] => Double;

lib/Affix.pod  view on Meta::CPAN

=back

=head2 Rust

Rust does not use the C ABI by default. You must explicitly instruct the compiler to format the function correctly.

=over

=item 1. B<Exporting:> Use C<#[no_mangle]> and C<pub extern "C">.

    #[no_mangle]
    pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }

=item 2. B<Structs:> Rust structs must be annotated with C<#[repr(C)]> to guarantee their memory layout matches C (and thus Affix's C<Struct>).

=item 3. B<Strings:> Rust strings are not null-terminated. You must receive C<String> arguments as C<*const std::os::raw::c_char> and convert them using C<CStr::from_ptr>.

=back

=head2 Fortran

Fortran relies heavily on pass-by-reference.

=over

=item 1. B<Pointers Everywhere:> Unless a parameter uses the modern Fortran C<VALUE> attribute, you must pass everything as a pointer. If the function expects a Float, your Affix signature must be C<Pointer[Float]>.

=item 2. B<Name Mangling:> Most Fortran compilers convert subroutine names to lowercase and append an underscore. A Fortran subroutine named C<CALC_STRESS> will likely be exported as C<calc_stress_>.

=item 3. B<Strings:> Fortran does not use null-terminated strings. When passing character arrays, Fortran compilers silently append hidden "length" parameters at the B<end> of the argument list (passed by value as integers).

=back

=head2 Assembly

When writing raw Assembly (NASM/GAS), you must manually adhere to the calling convention of your target platform:

=over

=item * B<Linux/macOS (System V AMD64 ABI):> Arguments are passed in C<rdi, rsi, rdx, rcx, r8, r9>, with the rest on the stack.

=item * B<Windows (Microsoft x64):> Arguments are passed in C<rcx, rdx, r8, r9>, with "shadow space" reserved on the stack.

=back

=head2 Go

Go libraries can be loaded if they are compiled with C<-buildmode=c-shared>. Note that Go slices and strings contain
internal metadata (length/capacity) and do not map directly to C arrays or C<char*>. Use the C<C> package inside Go
(C<import "C">) and C<*C.char> to bridge the boundary.

=head1 ERROR HANDLING & DEBUGGING

Diagnose FFI issues with built-in error reporting, memory inspection, and hex dumps. Bridging two entirely different
runtimes can lead to spectacular crashes if types or memory boundaries are mismatched.

=head2 Error Handling

=head3 C<errno()>

Accesses the system error code from the most recent FFI or standard library call (reads C<errno> on Unix and
C<GetLastError> on Windows).

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

Prints a formatted hex dump of the memory pointed to by a Pin directly to C<STDOUT>. This is an invaluable tool for
verifying that C structs or buffers contain the data you expect.

    my $ptr = strdup("Affix Debugging");
    dump($ptr, 16);

    # Output:
    # Dumping 16 bytes from 0x55E9A8A5 at script.pl line 42
    #  000  41 66 66 69 78 20 44 65 62 75 67 67 69 6e 67 00 | Affix Debugging.

=head3 C<sv_dump( $scalar )>

Dumps Perl's internal interpreter structure (SV) for a given scalar to C<STDOUT>. This exposes the raw flags, reference
counts, and memory layout of the Perl variable itself.

    my $val = 42;
    sv_dump($val);
    # Exposes IV flags, memory addresses of the SV head, etc.

=head2 Advanced Debugging

=head3 C<set_destruct_level( $level )>

Sets the internal C<PL_perl_destruct_level> variable.

When testing XS/FFI code for memory leaks using tools like Valgrind or AddressSanitizer, you often want Perl to
meticulously clean up all global memory during its destruction phase (otherwise the leak checker will be flooded with
false-positive "leaks" that are actually just memory Perl intentionally leaves to the OS to reclaim).

    # Call this at the start of your script when running under Valgrind
    set_destruct_level(2);

=head1 COMPANION MODULES



( run in 1.344 second using v1.01-cache-2.11-cpan-64ef6c95b5d )