In previous chapters, we bypassed C++ name mangling and wrapper shims by manually ripping the Virtual Method Table (vtable) out of a C++ object and calling the function pointers directly.
However, we had to perform a chore: because C++ methods always take a hidden this pointer as their first argument, we had to manually insert Pointer[Void] at the beginning of every single signature we wrapped. When dealing with complex signatures or callbacks, this boilerplate gets messy.
Affix provides a semantic shortcut for this exact scenario: the ThisCall modifier.
The Recipe
We will compile a C++ class, extract its vtable, and use ThisCall to dynamically generate the correct method wrapper.
use v5.40;
use Affix qw[:all];
use Affix::Build;
my $c = Affix::Build->new();
$c->add( \<<~'CPP', lang => 'cpp' );
#include <iostream>
class Greeter {
public:
virtual ~Greeter() {}
virtual void greet(const char* name) {
std::cout << "C++ says: Hello, " << name << "!" << std::endl;
}
};
// A standard C shim just to give us the object instance
extern "C" Greeter* new_greeter() {
return new Greeter();
}
CPP
my $lib = $c->link;
# 1. Get the object instance
affix $lib, 'new_greeter', [] => Pointer[Void];
my $obj = new_greeter();
# 2. Extract the VTable (Assuming Itanium ABI: gcc/clang)
# The first Size_t sized slot in the object is the pointer to the vtable.
my $vptr = cast( $obj, Pointer[Size_t] );
my $vtable_addr = $$vptr;
# Read three function-pointer slots out of the vtable.
# Slots 0 and 1 are usually the destructors; slot 2 is our virtual `greet`.
my $vtable = cast( $vtable_addr, Array[ Size_t, 3 ] );
my $greet_addr = $vtable->[2];
# 3. Bind the method
# The C++ method still receives the hidden `this` pointer as its first argument,
# so the signature leads with `*void` — but we no longer build a `Pointer[Void]`
# type object by hand, the signature string handles it.
my $greet = wrap( undef, $greet_addr, '(*void,*char)->void' );
# 4. Execute
# We still pass the object instance ($obj) as the first argument.
$greet->( $obj, "Affix Developer" );
Which prints:
C++ says: Hello, Affix Developer!
How It Works
-
1. Ripping Out the VTable The object is just memory whose first word is a pointer to its vtable.
cast( $obj, Pointer[Size_t] )treats that first word as an integer, and dereferencing ($$vptr) hands us the vtable's address. A secondcasttoArray[ Size_t, 3 ]exposes the vtable slots as plain integers we can index. -
2. The
ThisCallModifierThisCallis syntactic sugar that prepends the hiddenthispointer to a method signature. To the JIT engine, the signature(*void,*char)->voidmeans "a function taking(void* this, const char* name)and returning nothing" — exactly what the compiler generated forGreeter::greet. -
3. Method Execution When you invoke the wrapped subroutine (
$greet->($obj, "Affix Developer")), the JIT trampoline correctly places the$objpointer into the designated CPU register for the first argument (e.g.,RDIon SysV x64, orRCXon Windows x64). The C++ method receives thethispointer exactly where it expects it, and instance variables resolve correctly.
Kitchen Reminders
ThisCallandCallbackTypesThisCallshines when a C++ library expects you to provide a function pointer matching a class method signature. Wrap your callback inThisCall( Callback[ [ ... ] => ... ] )and the trampoline will hand your Perl subroutine the hiddenthispointer as its first argument, preventing off-by-one argument shifts:my $method = ThisCall( Callback( [ [ String ] => Void ] ) ); # method->signature is now '*((*void,*char)->void)'- Raw Signatures vs. Type Objects
wraptakes either an[args] => retpair (whereStringis a valid Affix type) or a raw function-pointer signature string (where the C syntax is(*void,*char)->void). Don't mix them: a signature string cannot useString— spell it*char. - ABI Fragility As mentioned in earlier chapters, vtable layouts are determined by the compiler's ABI. MSVC on Windows often places the first user-defined virtual method at slot 0 or 1, whereas GCC/Clang (including MinGW) places it at slot 2. Always double-check your target platform's vtable layout!
Comments