Sometimes you need to peek inside the oven. If a function expects a C style struct to be passed by value or if you want to read struct members without writing C helper functions, you can define the struct layout in Perl and Affix handles the rest.
The Recipe
We will create a simple geometry library that operates on Points and Rectangles.
use v5.40;
use Affix qw[:all];
use Affix::Build;
# 1. Compile C Library
my $c = Affix::Build->new();
$c->add( \<<~'END', lang => 'c' );
typedef struct {
int x, y;
} Point;
typedef struct {
Point top_left;
Point bottom_right;
int color;
} Rect;
// Pass by Value
int area(Rect r) {
int w = r.bottom_right.x - r.top_left.x;
int h = r.bottom_right.y - r.top_left.y;
return w * h;
}
// Pass by Reference (Modify in place)
void move_rect(Rect *r, int dx, int dy) {
r->top_left.x += dx;
r->top_left.y += dy;
r->bottom_right.x += dx;
r->bottom_right.y += dy;
}
END
my $lib = $c->link;
# 2. Define Types
# Order matters! Defined in the same order as the C struct.
typedef Point => Struct [ x => Int, y => Int ];
# We can nest types. Point() refers to the typedef above.
typedef Rect => Struct [ tl => Point(), br => Point(), color => Int ];
# 3. Bind
affix $lib, 'area', [ Rect() ] => Int;
affix $lib, 'move_rect', [ Pointer [ Rect() ], Int, Int ] => Void;
# 4. Use (Pass by Value)
# We represent the struct as a hash reference.
my $r = { tl => { x => 0, y => 0 }, br => { x => 10, y => 20 }, color => 0xFF00FF };
say 'Area: ' . area($r); # 200
# 5. Use (Pass by Reference)
# We pass a reference to the hash.
# Affix updates the hash members after the call.
move_rect( $r, 5, 5 );
say "New TL: $r->{tl}{x}, $r->{tl}{y}"; # 5, 5
How It Works
-
1. Structural typing
typedef Point => Struct [ x => Int, y => Int ];Affix (by way of infix's type introspection) calculates the memory layout (padding, alignment, size) of the struct matching the platform's C ABI.
-
2. Hashref marshalling
When you pass a hashref (
HV*) to aStructargument, Affix packs the hash values into a temporary C memory block. When the call returns, if you passed a reference (as in step 5), Affix automagically unpacks the C memory back into your hashref, seemingly updating any changed values. -
3. Nesting
Structs can contain other Structs, Arrays, and Pointers. Affix handles the recursive packing and unpacking automatically.
Kitchen Reminders
-
Performance
Marshalling a deep hashref into a C struct (and back) involves copying data. For high-performance scenarios (like processing millions of points), use
callocto allocate the struct in C memory once, and pass the pointer around. -
Missing keys
If your hashref is missing a key defined in the Struct's list of fields, Affix assumes
0(orNULL).