You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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(); }CPPmy$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 second cast to Array[ Size_t, 3 ] exposes the vtable slots as plain integers we can index.
2. The ThisCall Modifier ThisCall is syntactic sugar that prepends the hidden this pointer to a method signature. To the JIT engine, the signature (*void,*char)->void means "a function taking (void* this, const char* name) and returning nothing" — exactly what the compiler generated for Greeter::greet.
3. Method Execution
When you invoke the wrapped subroutine ($greet->($obj, "Affix Developer")), the JIT trampoline correctly places the $obj pointer into the designated CPU register for the first argument (e.g., RDI on SysV x64, or RCX on Windows x64). The C++ method receives the this pointer exactly where it expects it, and instance variables resolve correctly.
Kitchen Reminders
ThisCall and Callback Types ThisCall shines when a C++ library expects you to provide a function pointer matching a class method signature. Wrap your callback in ThisCall( Callback[ [ ... ] => ... ] ) and the trampoline will hand your Perl subroutine the hidden this pointer 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 wrap takes either an [args] => ret pair (where String is 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 use String — 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!
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
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
thispointer as their first argument, we had to manually insertPointer[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
ThisCallmodifier.The Recipe
We will compile a C++ class, extract its vtable, and use
ThisCallto dynamically generate the correct method wrapper.Which prints:
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: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.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!
All reactions