view release on metacpan or search on metacpan
## [v1.0.2] - 2025-12-14
### Changed
- In an attempt to debug mystery failures in SDL3.pm, Affix.pm will warn and return `undef` instead of `croak`ing.
- Improved error reporting: if the internal error message is empty, the numeric error code is now included in the warning.
### Fixed
- [[infix]] Fixed a critical file descriptor leak on POSIX platforms (Linux/FreeBSD) where the file descriptor returned by `shm_open` was kept open for the lifetime of the trampoline, eventually hitting the process file descriptor limit (EMFILE). T...
- Fixed memory leaks that occurred when trampoline creation failed midway (cleaning up partial arenas, strings, and backend structures).
## [v1.0.1] - 2025-12-13
### Changed
- Improved Union marshalling: Union members are now exposed as pins within the hash. This allows clean syntax (like `$u->{member} = 5`) without needing to dereference a reference, while maintaining C-memory aliasing.
### Fixed
Affix is a high-performance, developer friendly Foreign Function Interface (FFI) extension for Perl. It serves as a
universal bridge to the vast ecosystem of native software including those written in C, Rust, Zig, C++, Go, Fortran,
and more without writing XS code, managing a compiler, or compromising on execution speed. Affix also comes with an
extensive type system including native support for primitives (including half-width floats and 128bit integers), nested
C style structs, union, fixed size arrays, smart handling of enums, SIMD vector types, and, of course, pointers.
At its core, Affix is powered by [infix](https://github.com/sanko/infix/), a lightweight JIT (Just-In-Time) compilation
engine designed with speed and portability as its primary objectives. Unlike traditional FFI solutions that rely on
generic, per-call dispatch loops, Affix generates optimized machine code trampolines at runtime. These trampolines
handle argument marshalling and return value processing directly, significantly reducing the overhead of crossing the
boundary between Perl and native code. The underlying infix engine is [rigorously tested across a diverse range of
environments](https://github.com/sanko/infix/actions/workflows/ci.yml), ensuring reliable performance on Linux, Windows,
macOS, Solaris, and various BSD flavors. It supports multiple CPU architectures including `x86_64` and `AArch64`
(ARM64).
Affix serves as a universal bridge to the vast ecosystem of native software. Whether you're tapping into a legacy
Fortran math routine, a modern Rust crate, or a system-level C library, Affix makes the integration safe, idiomatic,
and exceptionally fast.
# EXPORTS
```
# CORE API
These functions are the primary entry points for interacting with foreign libraries.
## `affix( $lib, $symbol, $params, $return )`
Attaches a symbol from a library to a named Perl subroutine in the current namespace.
- **`$lib`**: A library handle returned by `load_library`, a string name, or `undef` to search the currently running process/executable.
- **`$symbol`**: The name of the C function. To install it under a different name in Perl, pass an array reference: `['c_name', 'perl_alias']`. To bind a raw memory address, pass it directly: `[$ptr, 'perl_alias']`.
- **`$params`**: An `ArrayRef` of Affix Type objects representing the function's arguments.
- **`$return`**: A single Affix Type object representing the return value.
```perl
# Standard: Load from library
affix $lib, 'pow', [ Double, Double ] => Double;
# Rename: Load 'pow', install as 'power' in Perl
affix $lib, [ pow => 'power' ], [ Double, Double ] => Double;
Creates a wrapper around a given symbol and returns it as an anonymous `CODE` reference. Arguments are identical to
`affix` except you cannot provide an alias.
```perl
my $pow = wrap $lib, 'pow', [ Double, Double ] => Double;
my $result = $pow->( 2, 5 );
```
## `direct_affix( ... )` / `direct_wrap( ... )`
**Experimental:** Bypasses standard safety checks and intermediate processing for maximum performance with simple
primitives. Generates highly specialized trampolines that read Perl SVs directly from the stack.
## `typedef( $name => $type )`
Registers a named type alias. This makes signatures more readable and is required for recursive types and smart Enums.
```perl
# C: typedef struct { int x; int y; } Point;
typedef Point => Struct[ x => Int, y => Int ];
```perl
my $lib = load_library('sqlite3');
```
**Lifecycle:** Library handles are thread-safe and internally reference-counted. The underlying OS library is only
closed (e.g., via `dlclose` or `FreeLibrary`) when all Affix wrappers and pins relying on it are destroyed.
_Note:_ When using `affix()` or `wrap()`, you can safely pass the string name directly (e.g., `affix('sqlite3',
...)`) and Affix will call `load_library` for you internally. If you pass `undef` instead of a library name, Affix
will search the currently running executable process.
### `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');
before starting any background threads or loops in the library.
Unsafe operations that you should never call from Callbacks or in a threaded context:
- `affix( ... )` - Binding new functions.
- `typedef( ... )` - Registering new types.
## 2. Callbacks
When passing a Perl subroutine as a `Callback`, avoid performing complex Perl operations like loading modules or
defining subs inside callbacks triggered on a foreign thread. Such callbacks should remain simple: process data, update
a shared variable, and return.
If the library executes the callback from a background thread (e.g., window managers, audio callbacks), Affix attempts
to attach a temporary Perl context to that thread. This should be sufficient but Perl is gonna be Perl.
# RECIPES & EXAMPLES
See [The Affix Cookbook](https://github.com/sanko/Affix.pm/discussions/categories/recipes) for comprehensive guides to
using Affix.
value => 1,
next => {
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] ]);
builder/Affix/Builder.pm view on Meta::CPAN
my @parts = ('Affix');
my $archdir = rel2abs catdir( curdir, qw[. blib arch auto], @parts );
my $err;
make_path( $archdir, { chmod => 0755, error => \$err, verbose => $verbose } );
my $lib_file = catfile( $archdir, $mod2fname->( \@parts ) . '.' . $Config{dlext} );
my @dirs;
push @dirs, '../';
my $has_cxx = !1;
my @sources = $cwd->child('lib/Affix.c');
#~ warn "Sources to process: @sources\n";
for my $source (@sources) {
#~ warn "Processing source: $source\n";
my $cxx = $source =~ /cx+$/;
my $file_base = $source->basename(qr[.c$]);
my $tempdir = path('lib');
$tempdir->mkdir( { verbose => $verbose, mode => oct '755' } );
my $version = $meta->version;
my $obj = $builder->object_file($source);
infix/LICENSE-CC view on Meta::CPAN
g. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
h. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
i. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
j. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
infix/LICENSE-CC view on Meta::CPAN
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the âLicensor.â The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
infix/src/arch/aarch64/abi_arm64.c view on Meta::CPAN
// If returning a large struct, the ABI requires the hidden pointer (our return buffer, in X20)
// to be passed in the indirect result location register, x8.
if (layout->return_value_in_memory)
emit_arm64_mov_reg(buf, true, X8_REG, X20_REG); // mov x8, x20
// Standard AAPCS64 Quirk: For variadic calls, a GPR must contain the number of VPRs used.
// This rule does NOT apply to Apple's ABI, so we exclude it for macOS.
#if !defined(INFIX_OS_MACOS)
else if (layout->is_variadic)
// Since we don't know the types of variadic arguments at compile time, the ABI
// states the safest value is 0. A callee like printf will use this to determine
// how to process its va_list. We use x8 as it's a volatile register.
// A safe default is 0. Callee (like printf) uses this to interpret its va_list.
emit_arm64_load_u64_immediate(buf, X8_REG, 0); // mov x8, #0
#endif
// Main argument marshalling loop.
for (size_t i = 0; i < num_args; ++i) {
infix_arg_location * loc = &layout->arg_locations[i];
infix_type * type = arg_types[i];
// Load the pointer to the current argument's data into scratch register x9.
// x21 holds the base of the void** args_array.
emit_arm64_ldr_imm(buf, true, X9_REG, X21_REG, (int32_t)(i * sizeof(void *))); // ldr x9, [x21, #offset]
infix/src/arch/aarch64/abi_arm64_common.h view on Meta::CPAN
*
* @internal
* This header serves two primary purposes for the AArch64 backend:
*
* 1. **Register Enumerations:** It defines enums for the general-purpose registers (GPRs) and
* the floating-point/SIMD registers (VPRs). These enums provide a clear, type-safe,
* and self-documenting way to refer to specific registers when emitting machine
* code or implementing the ABI logic. The comments on each register describe its
* role according to the standard AAPCS64 calling convention.
*
* 2. **Instruction Encoding Constants:** It contains preprocessor definitions for the
* fixed bitfields of various AArch64 instructions. This abstracts away the
* "magic numbers" of machine code generation, making the emitter code in
* `abi_arm64_emitters.c` more readable and easier to verify against the official
* ARM Architecture Reference Manual.
*
* By centralizing these definitions, this header provides a single source of truth for
* the low-level architectural details, separating them from the higher-level ABI logic.
* @endinternal
*/
#include <stdint.h>
infix/src/arch/x64/abi_sysv_x64.c view on Meta::CPAN
*num_classes = 1;
return;
}
// Run the recursive classification. If it returns true, an unaligned
// field was found, and the class is already set to MEMORY. We can stop.
size_t field_count = 0; // Initialize the counter for this aggregate.
if (classify_recursive(type, 0, classes, 0, &field_count, false)) { // Pass counter to initial call
*num_classes = 1;
return;
}
// Post-processing for alignment padding.
if (type->size > 0 && classes[0] == NO_CLASS)
classes[0] = INTEGER;
if (type->size > 8 && classes[1] == NO_CLASS)
classes[1] = INTEGER;
// Count the number of valid, classified eightbytes.
if (classes[0] != NO_CLASS)
(*num_classes)++;
if (classes[1] != NO_CLASS)
(*num_classes)++;
}
infix/src/arch/x64/abi_win_x64.c view on Meta::CPAN
emit_sub_reg_imm32(buf, RSP_REG, (int32_t)layout->total_stack_alloc);
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 3 (Reverse): Generates code to marshal arguments into the generic `void**` array.
* @details This function performs the "un-marshalling" of arguments from their native
* locations into the generic format expected by the C dispatcher.
*
* The process is as follows:
* 1. **Save All Argument Registers:** It first saves all four potential integer
* argument registers (RCX, RDX, R8, R9) and all four potential floating-point
* registers (XMM0-3) to a dedicated save area on the local stack. This
* captures all register-based arguments in one place.
*
* 2. **Populate `args_array`:** It then iterates through the function's expected
* arguments and generates code to populate the `args_array`. For each argument:
* a. It determines if the argument was passed in a register or on the stack.
* b. If passed by reference, it gets the pointer directly from the register
* save area or the caller's stack.
infix/src/common/compat_c23.h view on Meta::CPAN
#ifndef static_assert
#define static_assert(cond, msg) _Static_assert(cond, msg)
#endif
#endif
/**
* @def COMPAT_HAS_C_ATTRIBUTE
* @brief A utility macro to safely check for the existence of a C attribute.
*
* @details This macro wraps the `__has_c_attribute` feature test macro, which is
* not universally available across all compilers. By defining this wrapper,
* the code can safely check for attribute support without causing a preprocessor
* error on compilers that do not define `__has_c_attribute`.
*
* @param[in] x The name of the attribute to check (e.g., `nodiscard`).
* @return `1` if the attribute is supported, `0` otherwise.
*/
#if defined(__has_c_attribute)
#define COMPAT_HAS_C_ATTRIBUTE(x) __has_c_attribute(x)
#else
#define COMPAT_HAS_C_ATTRIBUTE(x) 0
#endif
infix/src/common/double_tap.h view on Meta::CPAN
fflush(stdout);
}
#else // OpenBSD or other platforms without robust pthread_once support in this context
static bool g_tap_initialized = false;
#endif
/**
* @internal
* @brief Ensures the TAP header has been printed and thread-local state is initialized.
* Uses `pthread_once` or `InitOnceExecuteOnce` to guarantee the TAP version header
* is printed exactly once per process, even with multiple threads. It also initializes
* the thread-local state for the current thread if it's the first test call on that thread.
*/
static void _tap_ensure_initialized(void) {
#if defined(_WIN32) || defined(__CYGWIN__)
InitOnceExecuteOnce(&g_tap_init_once, _tap_init_routine, NULL, NULL);
#elif (defined(__unix__) || defined(__APPLE__)) && !defined(__OpenBSD__)
pthread_once(&g_tap_init_once, _tap_init_routine);
#else
// Fallback for OpenBSD/single-threaded builds
if (!g_tap_initialized) {
infix/src/common/infix_config.h view on Meta::CPAN
* @brief Platform, architecture, and ABI detection macros.
* @ingroup internal_common
*
* @details This header is the first to be included by `infix_internals.h` and is
* responsible for defining a consistent set of `INFIX_*` macros that describe the
* build environment. It is the central point of configuration for the entire library,
* adapting the build to different operating systems, compilers, and CPU architectures.
*
* Its most critical function is to select the correct **Application Binary Interface (ABI)**
* implementation to use for JIT code generation. This is achieved through a cascade
* of preprocessor checks that can be overridden by the user for cross-compilation.
* By the end of this file, exactly one `INFIX_ABI_*` macro must be defined, which
* determines which `abi_*.c` file is included in the unity build.
*
* @internal
*/
#pragma once
// System Feature Test Macros
/**
* @details These macros are defined to ensure that standard POSIX and other
* necessary function declarations (like `dlopen`, `dlsym`, `snprintf`, `shm_open`)
infix/src/common/infix_config.h view on Meta::CPAN
*/
#if !defined(_POSIX_C_SOURCE)
#define _POSIX_C_SOURCE 200809L
#endif
#if (defined(__linux__) || defined(__gnu_linux__)) && !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
// Operating System Detection
/**
* @details This section defines `INFIX_OS_*` macros based on compiler-provided
* preprocessor definitions. It also defines the broader `INFIX_ENV_POSIX` for systems
* that follow POSIX conventions, which simplifies later `#ifdef` logic.
*/
#if defined(_WIN32)
#define INFIX_OS_WINDOWS
#include <windows.h> // Included early for common types like SYSTEM_INFO, HANDLE, etc.
// Compatibility shim for POSIX types not present in Clang/MSVC headers.
#if !defined(__CYGWIN__) // Cygwin provides its own full POSIX environment
#include <stddef.h> // For ptrdiff_t
#ifndef ssize_t
// Define ssize_t as ptrdiff_t, the standard signed counterpart to size_t.
infix/src/common/infix_config.h view on Meta::CPAN
#endif
#endif // INFIX_ABI_FORCED
// Miscellaneous Constants
/**
* @def INFIX_TRAMPOLINE_HEADROOM
* @brief Extra bytes to allocate in a trampoline's private arena.
*
* @details When a trampoline handle is created, it deep-copies all type information
* from a source (like the parser's temporary arena) into its own private arena.
* The size of the source arena is used as a hint for the new arena's size, but the
* copy process itself requires a small amount of extra memory for its own bookkeeping
* (e.g., the memoization list in `_copy_type_graph_to_arena_recursive`). This
* headroom provides that extra space to prevent allocation failures during the copy.
*/
#define INFIX_TRAMPOLINE_HEADROOM 128
/**
* @def INFIX_SANITY_CHECK_ENABLE
* @brief If defined and non-zero, the JIT will emit extra instructions to verify
* stack consistency around user-provided marshaller calls.
*/
infix/src/common/infix_internals.h view on Meta::CPAN
* @details This struct encapsulates the platform-specific details of allocating and
* managing executable memory in a way that is compliant with modern OS security
* features like W^X (Write XOR Execute). It supports two primary strategies:
*
* 1. **Single-Mapping W^X (Windows/macOS/Android):** A single memory region is
* allocated as Read-Write (`rw_ptr`). After the JIT compiler writes the
* machine code to this region, its permissions are changed to Read-Execute.
* In this model, `rx_ptr` and `rw_ptr` point to the same address.
*
* 2. **Dual-Mapping W^X (Linux/BSD):** A single underlying shared memory object
* is mapped into the process's address space twice: once as Read-Write
* (`rw_ptr`) and once as Read-Execute (`rx_ptr`). The pointers have different
* virtual addresses but point to the same physical memory. This is required
* on systems with stricter W^X enforcement.
*/
typedef struct {
#if defined(INFIX_OS_WINDOWS)
HANDLE handle; /**< The handle from `VirtualAlloc`, needed for `VirtualFree`. */
void * seh_registration; /**< (Windows x64) Opaque handle from `RtlAddFunctionTable`. */
#else
int shm_fd; /**< The file descriptor for shared memory on dual-mapping POSIX systems. -1 otherwise. */
infix/src/common/infix_internals.h view on Meta::CPAN
* @def INFIX_MAX_ARG_SIZE
* @brief A safety limit (64KB) for the size of a single argument.
*/
#define INFIX_MAX_ARG_SIZE (1024 * 64)
/**
* @enum infix_arg_location_type
* @brief Describes the physical location where a function argument is passed according to the ABI.
*
* This enumeration abstracts away the differences in how various ABIs use
* registers and the stack to pass data. It is the primary output of the ABI
* classification process.
*/
typedef enum {
/** @brief Argument is passed in a general-purpose integer register (e.g., `RCX`, `RDI`, `X0`). */
ARG_LOCATION_GPR,
#if defined(INFIX_ABI_AAPCS64)
/** @brief (AArch64) Argument is passed in a vector/floating-point register (e.g., `V0`). */
ARG_LOCATION_VPR,
/** @brief (AArch64) A struct <= 16 bytes passed in a pair of GPRs (e.g., `X0`, `X1`). */
ARG_LOCATION_GPR_PAIR,
/** @brief (AArch64) A large struct (> 16 bytes) passed by reference; the pointer is in a GPR. */
infix/src/common/infix_internals.h view on Meta::CPAN
ARG_LOCATION_INTEGER_SSE_PAIR,
/** @brief (SysV x64) A struct split between an SSE and a GPR register. */
ARG_LOCATION_SSE_INTEGER_PAIR,
#endif
/** @brief Argument is passed on the stack. */
ARG_LOCATION_STACK
} infix_arg_location_type;
/**
* @struct infix_arg_location
* @brief Detailed location information for a single function argument.
* @details This struct is the result of the ABI classification process for one
* argument. It provides all the information the code emitters need to generate
* the correct move/load/store instructions.
*/
typedef struct {
infix_arg_location_type type; /**< The classification of the argument's location. */
uint8_t reg_index; /**< The index of the primary register used. */
uint8_t reg_index2; /**< The index of the second register (for pairs). */
uint32_t num_regs; /**< Number of regs OR scratch buffer offset. */
uint32_t stack_offset; /**< The byte offset from the stack pointer. */
} infix_arg_location;
infix/src/common/infix_internals.h view on Meta::CPAN
*/
INFIX_INTERNAL void infix_executable_free(infix_executable_t exec);
typedef enum {
INFIX_EXECUTABLE_FORWARD,
INFIX_EXECUTABLE_SAFE_FORWARD,
INFIX_EXECUTABLE_REVERSE,
INFIX_EXECUTABLE_DIRECT
} infix_executable_category_t;
/**
* @brief Makes a block of JIT memory executable, completing the W^X process.
* @details Located in `src/jit/executor.c`. For single-map platforms, this calls
* `VirtualProtect` or `mprotect`. For dual-map platforms, this is a no-op. It
* also handles instruction cache flushing on relevant architectures like AArch64.
* @param exec The handle to the memory block to make executable.
* @param prologue_size The size of the generated prologue in bytes.
* @param category The type of trampoline being finalized.
* @return `true` on success, `false` on failure.
*/
INFIX_INTERNAL c23_nodiscard bool infix_executable_make_executable(infix_executable_t * exec,
infix_executable_category_t category,
infix/src/common/utility.h view on Meta::CPAN
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file utility.h
* @brief A header for conditionally compiled debugging utilities.
* @ingroup internal_utils
*
* @internal
* This header is the central point for the library's internal debugging infrastructure.
* Its primary feature is that it behaves differently based on the `INFIX_DEBUG_ENABLED`
* preprocessor macro. This allows debugging code to be seamlessly integrated
* during development without affecting the performance or size of the final
* production binary.
*
* - **When `INFIX_DEBUG_ENABLED` is defined and non-zero (Debug Mode):**
* - It declares the `infix_dump_hex` function for detailed memory inspection.
* - It defines the `INFIX_DEBUG_PRINTF` macro, which integrates with the `double_tap`
* test harness's logging system (`note()`) if available, or falls back to a
* standard `printf`. This allows debug messages from the core library to appear
* cleanly within the test output.
*
infix/src/common/utility.h view on Meta::CPAN
* output, formatted for readability.
*
* @param data A pointer to the start of the memory block to dump.
* @param size The number of bytes to dump.
* @param title A descriptive title to print before and after the hex dump.
*/
void infix_dump_hex(const void * data, size_t size, const char * title);
/**
* @internal
* @brief Declares the function prototype for `infix_dump_state` for use in debug builds.
* @details This function is an invaluable tool for inspecting the processor state.
*
* @param file The file name where the function is being called.
* @param title The line number.
*/
void infix_dump_state(const char * file, int line);
/**
* @internal
* @def INFIX_DUMP_STATE(...)
* @brief Automatically pass on the current file and line to `infix_dump_state`.
*/
infix/src/core/loader.c view on Meta::CPAN
* @return A pointer to an `infix_library_t` handle on success, or `nullptr` on failure.
* The returned handle must be freed with `infix_library_close`.
*/
INFIX_API INFIX_NODISCARD infix_library_t * infix_library_open(const char * path) {
infix_library_t * lib = infix_calloc(1, sizeof(infix_library_t));
if (lib == nullptr) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
#if defined(INFIX_OS_WINDOWS)
// On Windows, passing NULL to GetModuleHandleA returns a handle to the main executable file of the current process.
if (path == nullptr) {
lib->handle = GetModuleHandleA(path);
lib->is_pseudo_handle = true; // Mark this as a special, non-freeable handle.
}
else {
lib->handle = LoadLibraryA(path);
lib->is_pseudo_handle = false; // This is a regular, ref-counted handle.
}
#else
// Use RTLD_LAZY for performance (resolve symbols only when they are first used).
infix/src/core/signature.c view on Meta::CPAN
// Resolve and layout stages
status = _infix_resolve_type_graph_inplace(&final_func_type, registry);
if (status != INFIX_SUCCESS) {
infix_arena_destroy(*out_arena);
*out_arena = nullptr;
return INFIX_ERROR_INVALID_ARGUMENT;
}
_infix_type_recalculate_layout(final_func_type);
// Unpack the results for the caller from the final, processed function type object.
*out_ret_type = final_func_type->meta.func_ptr_info.return_type;
*out_args = final_func_type->meta.func_ptr_info.args;
*out_num_args = final_func_type->meta.func_ptr_info.num_args;
*out_num_fixed_args = final_func_type->meta.func_ptr_info.num_fixed_args;
return INFIX_SUCCESS;
}
// Type Printing Logic
/**
* @internal
infix/src/core/type_registry.c view on Meta::CPAN
}
resolve_memo_node_t * memo_head = nullptr;
infix_status status = _resolve_type_graph_inplace_recursive(temp_arena, type_ptr, registry, &memo_head);
infix_arena_destroy(temp_arena);
return status;
}
// Public API: Type Registration
/**
* @internal
* @struct _registry_parser_state_t
* @brief A minimal parser state for processing registry definition strings.
* @details This is distinct from the main signature `parser_state` because the
* grammar for definitions (`@Name=...;`) is much simpler and can be handled
* with a simpler, non-recursive parser.
*/
typedef struct {
const char * p; /**< The current position in the definition string. */
const char * start; /**< The start of the definition string for error reporting. */
} _registry_parser_state_t;
/**
* @internal
infix/src/core/types.c view on Meta::CPAN
*out_type = type;
return INFIX_SUCCESS;
}
// Internal Type Graph Management
/**
* @internal
* @struct recalc_visited_node_t
* @brief A node for a visited-list to prevent infinite recursion on cyclic types during layout calculation.
*
* @details This is a temporary structure, typically allocated on the main thread's heap,
* used during the layout recalculation process (`_infix_type_recalculate_layout_recursive`).
* It forms a singly-linked list that acts as a "visited set" for a depth-first
* traversal of the type graph. Its purpose is to detect and correctly handle cycles
* in type definitions (e.g., `struct Node { struct Node* next; };`) and prevent
* a stack overflow from infinite recursion.
*/
typedef struct recalc_visited_node_t {
infix_type * type; /**< The `infix_type` object that has been visited during the current traversal path. */
struct recalc_visited_node_t * next; /**< A pointer to the next node in the visited list. */
} recalc_visited_node_t;
/**
infix/src/core/utility.c view on Meta::CPAN
* Creative Commons Attribution 4.0 International License (CC BY 4.0).
*
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file utility.c
* @brief Implements internal debugging utilities.
* @ingroup internal_core
*
* @details This file provides the implementation for debugging functions that are
* conditionally compiled only when the `INFIX_DEBUG_ENABLED` preprocessor macro
* is defined and set to a non-zero value.
*
* In release builds, this entire translation unit is effectively empty, ensuring
* that debugging code has no impact on the final binary's size or performance.
* The corresponding function declarations in `utility.h` become empty inline
* stubs, which are optimized away by the compiler.
*/
// This file is only compiled if debugging is enabled.
#if defined(INFIX_DEBUG_ENABLED) && INFIX_DEBUG_ENABLED
// Use the double-tap test harness's `note` macro for debug printing if available.
infix/src/core/utility.c view on Meta::CPAN
}
}
print_line:
note(" %s", line_buf);
}
note("End of %s", title);
}
/**
* @internal
* @brief Declares the function prototype for `infix_dump_state` for use in debug builds.
* @details This function is an invaluable tool for inspecting the processor state.
*
* This function is only compiled in debug builds.
*
* @param file The file name where the function is being called.
* @param title The line number.
*/
void infix_dump_state(const char * file, int line) {
#if defined(INFIX_ARCH_X64)
printf("# Dumping x64 Register State at %s:%d\n", file, line);
volatile unsigned long long stack_dump[16];
infix/src/infix.c view on Meta::CPAN
#include "core/signature.c"
// 6. Dynamic Library Loader: Implements cross-platform `dlopen`/`dlsym`.
// (Depends on error handling, types, and arena).
#include "core/loader.c"
// 7. Type System: Defines and manages `infix_type` objects and graph algorithms.
// (Depends on the arena and error handling).
#include "core/types.c"
// 8. Debugging Utilities: Low-level helpers for logging and inspection.
// (No dependencies).
#include "core/utility.c"
// 9. Platform and processor feature detection.
// (No dependencies).
#include "core/platform.c"
// 10. Trampoline Cache: Deduplication logic.
#include "core/cache.c"
// 11. Trampoline Engine: The central JIT compiler.
// This must be last, as it depends on all other components and includes the
// final ABI- and architecture-specific C files itself.
#include "jit/trampoline.c"
infix/src/jit/executor.c view on Meta::CPAN
void ** pAlloc = (void **)dlsym(cf, "kCFAllocatorDefault");
if (pAlloc)
g_macos_apis.kCFAllocatorDefault = *pAlloc;
g_macos_apis.SecTaskCreateFromSelf = dlsym(sec, "SecTaskCreateFromSelf");
g_macos_apis.SecTaskCopyValueForEntitlement = dlsym(sec, "SecTaskCopyValueForEntitlement");
dlclose(cf);
dlclose(sec);
}
/**
* @internal
* @brief Checks if the current process has the `com.apple.security.cs.allow-jit` entitlement.
* @return `true` if the entitlement is present and set to true, `false` otherwise.
*/
static bool has_jit_entitlement(void) {
// Use pthread_once to ensure the dynamic loading happens exactly once, thread-safely.
static pthread_once_t init_once = PTHREAD_ONCE_INIT;
pthread_once(&init_once, initialize_macos_apis);
// Secure JIT path on macOS requires both the entitlement check and the toggle API.
if (!g_macos_apis.pthread_jit_write_protect_np)
return false;
infix/src/jit/executor.c view on Meta::CPAN
* @brief Creates an anonymous file descriptor suitable for dual-mapping.
*
* @details Attempts multiple strategies in order of preference:
* 1. `memfd_create`: Modern Linux (kernel 3.17+). Best for security (no filesystem path).
* 2. `shm_open(SHM_ANON)`: FreeBSD/DragonFly. Automatic anonymity.
* 3. `shm_open(random_name)`: Fallback for older Linux/POSIX. Manually unlinked immediately.
*/
static int create_anonymous_file(void) {
#if defined(INFIX_OS_LINUX) && defined(MFD_CLOEXEC)
// Strategy 1: memfd_create (Linux 3.17+)
// MFD_CLOEXEC ensures the FD isn't leaked to child processes.
int linux_fd = memfd_create("infix_jit", MFD_CLOEXEC);
if (linux_fd >= 0)
return linux_fd;
// If it fails (e.g. old kernel, ENOSYS), fall through to shm_open.
#endif
#if defined(__FreeBSD__) && defined(SHM_ANON)
// Strategy 2: SHM_ANON (FreeBSD)
int bsd_fd = shm_open(SHM_ANON, O_RDWR | O_CREAT | O_EXCL, 0600);
if (bsd_fd >= 0)
return bsd_fd;
#endif
// Strategy 3: shm_open with randomized name (Legacy POSIX)
char shm_name[64];
uint64_t random_val = 0;
// Generate a sufficiently random name to avoid collisions if multiple processes
// are running this code simultaneously. Using /dev/urandom is a robust way to do this.
int rand_fd = open("/dev/urandom", O_RDONLY);
if (rand_fd < 0)
return -1;
ssize_t bytes_read = read(rand_fd, &random_val, sizeof(random_val));
close(rand_fd);
if (bytes_read != sizeof(random_val))
return -1;
snprintf(shm_name, sizeof(shm_name), "/infix-jit-%d-%llx", getpid(), (unsigned long long)random_val);
infix/src/jit/executor.c view on Meta::CPAN
if (exec.rx_ptr && exec.rx_ptr != exec.rw_ptr) // rw_ptr might be same as rx_ptr on some platforms
munmap(exec.rx_ptr, exec.size);
if (exec.shm_fd >= 0)
close(exec.shm_fd);
#endif
}
/**
* @internal
* @brief Makes a block of JIT memory executable and flushes instruction caches.
*
* @details This function completes the W^X process.
* - On single-mapping platforms, it changes the memory protection from RW to RX.
* - On dual-mapping platforms, this is a no-op as the RX mapping already exists.
*
* Crucially, it also handles flushing the CPU's instruction cache on architectures
* that require it (like AArch64). This is necessary because the CPU may have cached
* old (zeroed) data from the memory location, and it must be explicitly told to
* re-read the newly written machine code instructions.
*
* @param exec The executable memory block.
* @param category The category of the trampoline.
infix/src/jit/trampoline.c view on Meta::CPAN
extern const infix_direct_forward_abi_spec g_sysv_x64_direct_forward_spec;
#elif defined(INFIX_ABI_AAPCS64)
extern const infix_forward_abi_spec g_arm64_forward_spec;
extern const infix_reverse_abi_spec g_arm64_reverse_spec;
extern const infix_direct_forward_abi_spec g_arm64_direct_forward_spec;
#endif
/**
* @internal
* @brief Retrieves a pointer to the ABI specification v-table for forward calls.
* @details This function is the entry point to the ABI abstraction layer. It uses
* compile-time preprocessor macros (defined in `infix_config.h`) to select and
* return the correct v-table for the target platform.
* @return A pointer to the active `infix_forward_abi_spec`, or `nullptr` if the
* platform is unsupported.
*/
const infix_forward_abi_spec * get_current_forward_abi_spec() {
#if defined(INFIX_ABI_WINDOWS_X64)
return &g_win_x64_forward_spec;
#elif defined(INFIX_ABI_SYSV_X64)
return &g_sysv_x64_forward_spec;
#elif defined(INFIX_ABI_AAPCS64)
lib/Affix.c view on Meta::CPAN
symbol = INT2PTR(void *, SvUV(target_sv));
is_raw_ptr_target = true;
}
// Symbol lookup (unles it's a raw pointer)
const char * symbol_name_str = nullptr;
const char * rename_str = nullptr;
infix_library_t * lib_handle_for_symbol = nullptr;
bool created_implicit_handle = false;
// We only process names/libraries if we don't already have a raw pointer address
if (!is_raw_ptr_target) {
SV * name_sv = ST(1);
// Handle rename: affix($lib, ['real', 'alias'], ...)
if (SvROK(name_sv) && SvTYPE(SvRV(name_sv)) == SVt_PVAV) {
if (ix == 1 || ix == 3) // wrap/direct_wrap
croak("Cannot rename an anonymous Affix'd wrapper");
AV * name_av = (AV *)SvRV(name_sv);
if (av_count(name_av) != 2)
lib/Affix.c view on Meta::CPAN
SV * entry_sv = HeVAL(he);
LibRegistryEntry * entry = INT2PTR(LibRegistryEntry *, SvIV(entry_sv));
if (entry) {
#if DEBUG > 0
if (entry->ref_count > 0)
warn("Affix: library handle for '%s' has %d outstanding references at END.",
HeKEY(he),
(int)entry->ref_count);
#endif
// Temp fix: Disable library unloading at process exit.
//
// Many modern C libraries (WebUI, Go runtimes, Audio libs) spawn background
// threads that persist until the process dies. If we dlclose() the library
// here, the code segment is unmapped. When the background thread wakes up
// to do cleanup or work, it executes garbage memory and segfaults.
//
// Since the process is ending, the OS will reclaim file handles and memory
// automatically. It's (in my opinion) safer to leak the handle than to crash the process.
#if defined(__linux__) || defined(__linux)
// Leak the library handle but free our wrapper
if (entry->lib)
infix_free(entry->lib);
#else
// This extra symbol check is here to prevent shared libs written in Go from crashing Affix.
// The issue is that Go inits the full Go runtime when the lib is loaded but DOES NOT STOP
// IT when the lib is unloaded. Threads and everything else still run and we crash when perl
// exits. This only happens on Windows.
// See:
lib/Affix.pod view on Meta::CPAN
Affix is a high-performance, developer friendly Foreign Function Interface (FFI) extension for Perl. It serves as a
universal bridge to the vast ecosystem of native software including those written in C, Rust, Zig, C++, Go, Fortran,
and more without writing XS code, managing a compiler, or compromising on execution speed. Affix also comes with an
extensive type system including native support for primitives (including half-width floats and 128bit integers), nested
C style structs, union, fixed size arrays, smart handling of enums, SIMD vector types, and, of course, pointers.
At its core, Affix is powered by L<infix|https://github.com/sanko/infix/>, a lightweight JIT (Just-In-Time) compilation
engine designed with speed and portability as its primary objectives. Unlike traditional FFI solutions that rely on
generic, per-call dispatch loops, Affix generates optimized machine code trampolines at runtime. These trampolines
handle argument marshalling and return value processing directly, significantly reducing the overhead of crossing the
boundary between Perl and native code. The underlying infix engine is L<rigorously tested across a diverse range of
environments|https://github.com/sanko/infix/actions/workflows/ci.yml>, ensuring reliable performance on Linux, Windows,
macOS, Solaris, and various BSD flavors. It supports multiple CPU architectures including C<x86_64> and C<AArch64>
(ARM64).
Affix serves as a universal bridge to the vast ecosystem of native software. Whether you're tapping into a legacy
Fortran math routine, a modern Rust crate, or a system-level C library, Affix makes the integration safe, idiomatic,
and exceptionally fast.
=head1 EXPORTS
lib/Affix.pod view on Meta::CPAN
=head1 CORE API
These functions are the primary entry points for interacting with foreign libraries.
=head2 C<affix( $lib, $symbol, $params, $return )>
Attaches a symbol from a library to a named Perl subroutine in the current namespace.
=over
=item * B<C<$lib>>: A library handle returned by C<load_library>, a string name, or C<undef> to search the currently running process/executable.
=item * B<C<$symbol>>: The name of the C function. To install it under a different name in Perl, pass an array reference: C<['c_name', 'perl_alias']>. To bind a raw memory address, pass it directly: C<[$ptr, 'perl_alias']>.
=item * B<C<$params>>: An C<ArrayRef> of Affix Type objects representing the function's arguments.
=item * B<C<$return>>: A single Affix Type object representing the return value.
=back
# Standard: Load from library
lib/Affix.pod view on Meta::CPAN
=head2 C<wrap( $lib, $symbol, $params, $return )>
Creates a wrapper around a given symbol and returns it as an anonymous C<CODE> reference. Arguments are identical to
C<affix> except you cannot provide an alias.
my $pow = wrap $lib, 'pow', [ Double, Double ] => Double;
my $result = $pow->( 2, 5 );
=head2 C<direct_affix( ... )> / C<direct_wrap( ... )>
B<Experimental:> Bypasses standard safety checks and intermediate processing for maximum performance with simple
primitives. Generates highly specialized trampolines that read Perl SVs directly from the stack.
=head2 C<< typedef( $name => $type ) >>
Registers a named type alias. This makes signatures more readable and is required for recursive types and smart Enums.
# C: typedef struct { int x; int y; } Point;
typedef Point => Struct[ x => Int, y => Int ];
# C: typedef double Vector3[3];
lib/Affix.pod view on Meta::CPAN
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;
lib/Affix.pod view on Meta::CPAN
=item * C<affix( ... )> - Binding new functions.
=item * C<typedef( ... )> - Registering new types.
=back
=head2 2. Callbacks
When passing a Perl subroutine as a C<Callback>, avoid performing complex Perl operations like loading modules or
defining subs inside callbacks triggered on a foreign thread. Such callbacks should remain simple: process data, update
a shared variable, and return.
If the library executes the callback from a background thread (e.g., window managers, audio callbacks), Affix attempts
to attach a temporary Perl context to that thread. This should be sufficient but Perl is gonna be Perl.
=head1 RECIPES & EXAMPLES
See L<The Affix Cookbook|https://github.com/sanko/Affix.pm/discussions/categories/recipes> for comprehensive guides to
using Affix.
lib/Affix.pod view on Meta::CPAN
value => 1,
next => {
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
lib/Affix/Build.pm view on Meta::CPAN
# Check if we are mixing languages
my %langs = map { $_->{lang} => 1 } @sources;
if ( ( scalar keys %langs ) > 1 ) {
return $self->_strategy_polyglot();
}
return $_lib = $self->_strategy_native();
}
method link { $_lib //= $self->compile_and_link(); $_lib }
# Used when only one language is present. We delegate the entire build process
# to that language's compiler to produce the final shared library.
method _strategy_native () {
my $src = $sources[0];
my $l = $src->{lang};
my $handler = $self->_resolve_handler($l);
return $self->$handler( $src, $libname, 'dynamic' );
}
# Used when mixing languages (e.g. C + Rust). We compile everything to
# static artifacts (.o or .a) and then use the system linker to combine them.
lib/Affix/Build.pod view on Meta::CPAN
language of choice and instantly bind to it from Perl using L<Affix>.
=head2 Build Strategies
The builder automatically selects the optimal strategy based on the input sources:
=over
=item 1. B<Native Toolchain Strategy> (Single Language)
If you provide source files for only B<one> language, C<Affix::Build> delegates the entire build process to that
language's native toolchain (e.g., C<go build -buildmode=c-shared>, C<rustc --crate-type cdylib>, C<dotnet publish>).
This ensures that the language's standard library and runtime environment are configured exactly as intended by its
maintainers.
=item 2. B<Polyglot Aggregation Strategy> (Mixed Languages)
If you provide source files from B<multiple> languages (e.g., C code calling into a Rust library), the builder switches
to an aggregation strategy:
=over
lib/Affix/Build.pod view on Meta::CPAN
=item * B<File Path:> If C<$input> is a string, it is treated as a file path. The compiler auto-detects the language based on the extension.
=item * B<Inline Code:> If C<$input> is a SCALAR reference, you B<must> provide the C<lang> argument (e.g., C<'c'>, C<'rust'>) so the compiler knows how to handle it.
=item * B<C<flags>>: An ArrayRef or space-separated string of compiler flags specific to this source file.
=back
=head2 C<compile_and_link( )>
Performs the build process:
=over
=item 1. Inspects added sources to determine the Build Strategy.
=item 2. Compiles all sources to their appropriate intermediate or final formats.
=item 3. Links the artifacts (if necessary) into a shared library.
=back
lib/Affix/Wrap.pm view on Meta::CPAN
my $found = 0;
for my $pd (@$project_dirs) {
if ( $ep_dir eq $pd ) { $found = 1; last; }
}
push @$project_dirs, $ep_dir unless $found;
my @includes = map { "-I" . $self->_normalize($_) } @$include_dirs;
for my $d (@$project_dirs) { push @includes, "-I$d"; }
my @cmd = (
$clang, '-target', $self->_get_triple(), '-Xclang',
'-ast-dump=json', '-Xclang', '-detailed-preprocessing-record', '-fsyntax-only',
'-fparse-all-comments', '-Wno-everything', @includes, $ep_abs
);
my ( $stdout, $stderr, $exit ) = Capture::Tiny::capture { system(@cmd); };
if ( $exit != 0 ) { die "Clang Error:\n$stderr"; }
if ( $stdout =~ /^.*?(\{.*)/s ) { $stdout = $1; }
my $ast = JSON::PP::decode_json($stdout);
my @objects;
$self->_walk( $ast, \@objects, $ep_abs );
$self->_scan_macros_fallback( \@objects );
$self->_merge_typedefs( \@objects );
lib/Affix/Wrap.pod view on Meta::CPAN
=over
=item * C<affix_type>: Returns string C<pin my $var, $lib, name =E<gt> Type>.
=item * C<affix( $lib, $pkg )>: Installs the variable accessor into C<$pkg>.
=back
=head2 Affix::Wrap::Macro
A preprocessor C<#define>. Only simple value macros are captured.
=over
=item * C<affix_type>: Returns string C<use constant Name =E<gt> Value>. Expressions (e.g., C<A + B>) are quoted as strings, while literals are preserved.
=item * C<affix( undef, $pkg )>: Installs the constant into C<$pkg>.
=back
=head1 Tutorials
lib/Test2/Tools/Affix.pm view on Meta::CPAN
ref $hash->{$tag} eq 'ARRAY' ?
[ @{ $hash->{$tag} }, $content ] :
[ $hash->{$tag}, $content ] :
$tag =~ m/^(error|stack)$/ ? [$content] :
dec_ent($content) ) :
undef;
}
$hash;
}
# Function to run anonymous sub in a new process with valgrind
sub leaks( $name, $code_ref ) {
init_valgrind();
#
require B::Deparse;
CORE::state $deparse //= B::Deparse->new(qw[-l]);
my ( $package, $file, $line ) = caller;
my $source = sprintf
<<'', ( join ', ', map {"'$_'"} sort { length $a <=> length $b } grep {defined} map { my $dir = path($_); $dir->exists ? $dir->absolute->realpath : () } @INC, 't/lib' ), Test2::API::test2_stack()->top->{count}, $deparse->coderef2text(...
use lib %s;
use Test2::V0 -no_srand => 1, '!subtest';
t/004_typedef.t view on Meta::CPAN
if (!r) return -1;
return r->bottom_right.x - r->top_left.x;
}
typedef union {
int i;
float f;
char c[8];
} MyUnion;
DLLEXPORT float process_union_float(MyUnion u) {
return u.f * 10.0;
}
DLLEXPORT int read_union_int(MyUnion u) {
return u.i;
}
// Takes a callback that processes a struct
DLLEXPORT double process_struct_with_cb(MyStruct* s, double (*cb)(MyStruct*)) {
return cb(s);
}
// Takes a callback that returns a struct
DLLEXPORT int check_returned_struct_from_cb(Point (*cb)(void)) {
Point p = cb();
return p.x + p.y;
}
END_C
#
t/004_typedef.t view on Meta::CPAN
isa_ok my $sum_ids = wrap( $lib_path, 'sum_struct_ids', '(*@MyStruct, int32)->int32' ), ['Affix'];
my $struct_array
= [ { id => 10, value => 1.1, label => 'A' }, { id => 20, value => 2.2, label => 'B' }, { id => 30, value => 3.3, label => 'C' }, ];
is $sum_ids->( $struct_array, 3 ), 60, 'Correctly passed an array of structs and summed IDs';
};
subtest 'Forward Calls: Enums and Unions (with Typedefs)' => sub {
plan 4;
note 'Testing marshalling for enums and unions.';
isa_ok my $check_color = wrap( $lib_path, 'check_color', '(int32)->int32' ), ['Affix'];
is $check_color->(1), 1, 'Correctly passed an enum value (GREEN)';
isa_ok my $process_union = wrap( $lib_path, 'process_union_float', '(@MyUnion)->float32' ), ['Affix'];
my $union_data = { f => 2.5 };
is $process_union->($union_data), float(25.0), 'Correctly passed a union with the float member active';
};
subtest 'Forward Calls: Nested Structs and By-Value Returns (with Typedefs)' => sub {
plan 4;
isa_ok my $get_width = wrap( $lib_path, 'get_rect_width', '(*@RectPlus)->int32' ), ['Affix'];
is $get_width->( \{ top_left => { x => 10, y => 20 }, bottom_right => { x => 60, y => 80 }, name => 'My Rectangle' } ), 50,
'Correctly passed nested struct and calculated width';
isa_ok my $create_point = wrap( $lib_path, 'create_point', '(int32, int32)->@Point' ), ['Affix'];
my $point = $create_point->( 123, 456 );
is $point, { x => 123, y => 456 }, 'Correctly received a struct returned by value';
};
subtest 'Advanced Structs and Unions' => sub {
affix $lib_path, 'sum_point_by_val', '(@Point)->int';
my $point_hash = { x => 10, y => 25 };
is( sum_point_by_val($point_hash), 35, 'Correctly passed a struct by value' );
affix $lib_path, 'read_union_int', '(@MyUnion)->int';
my $union_hash = { i => 999 };
is( read_union_int($union_hash), 999, 'Correctly read int member from a C union' );
};
subtest 'Advanced Callbacks (Reverse FFI) (with Typedefs)' => sub {
diag 'Testing callbacks that send and receive structs by passing coderefs directly.';
isa_ok my $harness1 = wrap( $lib_path, 'process_struct_with_cb', '(*@MyStruct, (*(@MyStruct))->float64)->float64' ), ['Affix'];
my $struct_to_pass = { id => 100, value => 5.5, label => 'Callback Struct' };
my $cb1 = sub ($struct_ref) {
# Struct Pointer comes as a Pin (Scalar Ref). Dereference it.
my $struct = $$struct_ref;
return $struct->{value} * 2;
};
is $harness1->( $struct_to_pass, $cb1 ), 11.0, 'Callback coderef received struct pointer and returned correct value';
isa_ok my $harness2 = wrap( $lib_path, 'check_returned_struct_from_cb', '( *(()->void )->@Point )->int32' ), ['Affix'];
is $harness2->(
t/023_sockaddr.t view on Meta::CPAN
// Minimal definition to ensure struct layout matches system
#if defined(_WIN32)
#include <winsock2.h>
#else
#include <netinet/in.h>
#endif
// We implement a manual byte swap to verify the data arrived correctly
// without needing to link against system network libraries (ws2_32.dll etc)
// which simplifies the test build process.
DLLEXPORT int get_port_raw(struct sockaddr_in* sa) {
if (!sa) return -1;
// Return raw network-byte-order value
return sa->sin_port;
}
DLLEXPORT unsigned long get_addr(struct sockaddr_in* sa) {
if (!sa) return 0;
return sa->sin_addr.s_addr;
}