In C, OOP is often simulated using structs containing function pointers. This pattern is commonly known as a vtable and allows a library to call different implementations of a function depending on the object it's holding. With Affix, you can populate these fields with Perl subroutines, effectively creating a Perl class that C can call into.
We're going to use a vtable to build a hypothetical plugin system. The C library expects a struct containing handlers for init and process.
The Recipe
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 {
const char* name;
int (*init)(void);
void (*process)(const char* data);
} Plugin;
void run_plugin(Plugin* p, const char* data) {
if (p->init()) {
p->process(data);
}
}
END
my $lib = $c->link;
# 2. Define Types
# The struct fields must include the Callback signature.
typedef Plugin => Struct [ name => String, init => Callback [ [] => Int ], process => Callback [ [String] => Void ] ];
# 3. Bind
affix $lib, 'run_plugin', [ Pointer [ Plugin() ], String ] => Void;
# 4. Create the Perl "Object"
# We use a HashRef to represent the struct.
# The callback fields are populated with anonymous subs.
my $my_plugin = {
name => 'PerlPlugin v1.0',
init => sub {
say 'Initializing Perl Plugin...';
return 1; # Success
},
process => sub ($data) {
say 'Processing data in Perl: ' . $data;
}
};
# 5. Call C
# We pass the reference to our hash. Affix packs it into the C struct,
# creating trampolines for the subroutines automatically.
run_plugin( $my_plugin, 'Some Input Data' );
How It Works
-
1. Nested callbacks
init => Callback[ [] => Int ]When you define a struct member as a
Callback, Affix expects a Perl code reference in that hash field. -
2. Automatic trampolining
When you pass the HashRef
$my_pluginto the C function, Affix:- Allocates the C struct.
- Copies the string 'PerlPlugin v1.0'.
- Asks infix to generate reverse trampolines for the
initandprocesssubs. - Stores the function pointers to those trampolines in the struct and passes the whole thing on to our library.
-
3. Execution
When C calls
p->process(data), it hits the trampoline which bounces everything over to your Perl sub.
Kitchen Reminders
-
Lifetime management
The trampolines generated for struct members exist only for the duration of the call to
run_plugin. If the C library stores this struct pointer globally and tries to call the callbacks afterrun_pluginreturns, the program will crash.If you need persistent callbacks, you must manually allocate the struct using
callocand assign the fields, ensuring the Perl variables stay in scope.