Affix
view release on metacpan or search on metacpan
t/007_pointers.t view on Meta::CPAN
# Write the entire modified array ref back to the C pointer
$$arr_ptr = $array_values;
# Visual evidence that the memory has actually been updated
#~ Affix::dump( $arr_ptr, 32 );
# sum_int_array takes *int, so passing [8:int] (array ref) works as pointer
ok affix( $lib_path, 'sum_int_array', [ Pointer [Int], Int ], Int ), 'affix ... "sum_int_array", ...';
is sum_int_array( $arr_ptr, 8 ), 90, 'realloc successfully resized memory';
#
isa_ok my $check_is_null = wrap( $lib_path, 'check_is_null', '(*void)->bool' ), ['Affix'];
ok $check_is_null->(undef), 'Passing undef to a *void argument is received as NULL';
subtest 'char*' => sub {
isa_ok my $get_string = wrap( $lib_path, 'get_hello_string', '()->*char' ), ['Affix'];
is $get_string->(), 'Hello from C', 'Correctly returned a C string';
isa_ok my $set_string = wrap( $lib_path, 'set_hello_string', '(*char)->bool' ), ['Affix'];
ok $set_string->('Hello from Perl'), 'Correctly passed a string to C';
};
subtest 'int32*' => sub {
isa_ok my $deref = wrap( $lib_path, 'deref_and_add', '(*int32)->int32' ), ['Affix'];
isa_ok my $modify = wrap( $lib_path, 'modify_int_ptr', '(*int32, int32)->void' ), ['Affix'];
my $int_var = 50;
is $deref->( \$int_var ), 60, 'Passing a scalar ref as an "in" pointer works';
$modify->( \$int_var, 999 );
is $int_var, 1000, 'C function correctly modified the value in our scalar ref ("out" param)';
};
subtest 'void*' => sub {
isa_ok my $read_void = wrap( $lib_path, 'read_int_from_void_ptr', '(*void)->int32' ), ['Affix'];
my $int_val = 12345;
is $read_void->( \$int_val ), 12345, 'Correctly passed a scalar ref as a void* and read its value';
};
subtest 'char**' => sub {
isa_ok my $check_ptr_ptr = wrap( $lib_path, 'check_string_ptr_ptr', '(**char)->int32' ), ['Affix'];
my $string = 'perl';
ok $check_ptr_ptr->( \$string ), 'Correctly passed a reference to a string as char**';
is $string, 'C changed me', 'C function was able to modify the inner pointer';
};
subtest 'Struct Pointers (*@My::Struct)' => sub {
ok typedef( 'My::Struct' => Struct [ id => SInt32, value => Float64, label => Pointer [Char] ] ), q[typedef('My::Struct' = ...)];
isa_ok my $init_struct = wrap( $lib_path, 'init_struct', '(*@My::Struct, int32, float64, *char)->void' ), ['Affix'];
my %struct_hash;
$init_struct->( \%struct_hash, 101, 9.9, "Initialized" );
is \%struct_hash, { id => 101, value => float(9.9), label => "Initialized" }, 'Correctly initialized a Perl hash via a struct pointer';
isa_ok my $get_ptr = wrap( $lib_path, 'get_static_struct_ptr', '()->*@My::Struct' ), ['Affix'];
my $struct_ptr = $get_ptr->();
# Struct pointer now returns a Pin (Scalar Ref). Dereference it to check contents.
is $$struct_ptr, { id => 99, value => float(-1.0), label => 'Global' }, 'Dereferencing a returned struct pointer works';
};
subtest 'Function Pointers (*(int->int))' => sub {
isa_ok my $harness = wrap( $lib_path, 'call_int_cb', '(*((int32)->int32), int32)->int32' ), ['Affix'];
my $result = $harness->( sub { $_[0] * 10 }, 7 );
is $result, 70, 'Correctly passed a simple coderef as a function pointer';
ok $check_is_null->(undef), 'Passing undef as a function pointer is received as NULL';
};
subtest 'Memory Management (malloc, calloc, free)' => sub {
my $ptr = malloc(32);
ok $ptr, 'malloc returns a pinned SV*';
#~ use Data::Printer;
#~ p $ptr;
#~ diag length $ptr;
#~ diag Affix::dump( $ptr, 32 );
ok my $array_ptr = calloc( 4, Int ), 'calloc returns an array';
#~ diag Affix::dump( $array_ptr, 32 );
ok $array_ptr, 'calloc returns an Affix::Pointer object';
is sum_int_array( $array_ptr, 4 ), 0, 'Memory from calloc is zero-initialized';
ok free($array_ptr), 'Explicitly calling free() returns true';
# Note: Double-free would crash, so we assume it worked.
like( warning { free( find_symbol( load_library($lib_path), 'sum_int_array' ) ) },
qr/unmanaged/, 'free() croaks when called on an unmanaged pointer' );
# Test that auto-freeing via garbage collection doesn't crash
subtest 'GC of managed pointers' => sub {
ok my $scoped_ptr = malloc(16), 'malloc(16)';
#~ ok cast( $scoped_ptr, '*int'), 'cast void pointer to int pointer';
#substr $$scoped_ptr, 0, 1, 'a';
#~ diag '[' . ($$scoped_ptr) . ']';
my $values = $$scoped_ptr;
substr( $values, 4 ) = 'hi';
$$scoped_ptr = $values;
#~ Affix::dump( $scoped_ptr, 32 );
#~ diag '[' . ($$scoped_ptr) . ']';
# When $scoped_ptr goes out of scope here, its DESTROY method is called.
};
pass('Managed pointer went out of scope without crashing');
};
subtest 'Pointer Arithmetic and String Utils' => sub {
imported_ok qw[ptr_add ptr_diff strdup strnlen is_null];
subtest 'ptr_add and ptr_diff' => sub {
my $buf = calloc( 10, Int ); # 40 bytes
ok !is_null($buf), 'buffer is not null';
my $p2 = ptr_add( $buf, 8 ); # width of 2 ints
is ptr_diff( $p2, $buf ), 8, 'ptr_diff calculates 8 bytes';
is ptr_diff( $buf, $p2 ), -8, 'ptr_diff calculates -8 bytes';
$$p2 = 999; # Write to offset
# Verify via original array pointer
my $arr = cast( $buf, Array [ Int, 10 ] );
is $$arr->[2], 999, 'ptr_add moved to index 2 correctly';
free($buf);
};
subtest 'strdup and strnlen' => sub {
my $str = "Hello World";
my $dup = strdup($str);
ok !is_null($dup), 'strdup returned non-null';
is cast( $dup, String ), $str, 'strdup content matches';
is strnlen( $dup, 5 ), 5, 'strnlen capped at max';
is strnlen( $dup, 100 ), 11, 'strnlen found true length';
# Ensure it's managed memory that we can free
ok free($dup), 'free(dup) worked';
};
};
subtest 'return malloc\'d pointer' => sub {
ok affix( $lib_path, 'test', [] => Pointer [Void] ), 'affix test()';
# We MUST bind C's free, because Affix::free uses Perl's allocator.
# Mixing them causes crashes on Windows.
ok affix( $lib_path, 'c_free', [ Pointer [Void] ] => Void ), 'affix c_free()';
ok my $string = test(), 'test()';
is Affix::cast( $string, String ), 'Testing', 'read C string';
# Correct cleanup: Use the allocator that created it.
c_free($string);
pass('freed via c_free');
};
subtest 'deep pointers' => sub {
# Deep Indirection (***int)
isa_ok my $set_deep = wrap( $lib_path, 'set_int_deep', [ Pointer [ Pointer [ Pointer [Int] ] ], Int ] => Void ), ['Affix'];
# Manually construct the pointer chain with correct types
# Keep original 'malloc' pointers alive (managed) while using 'cast' aliases
# Layer 1: The int value (int*)
my $p_mem = malloc(8);
my $p_val = Affix::cast( $p_mem, Pointer [Int] );
# Assigning directly ($p_val = 0) would overwrite the magic scalar with a normal SV*
$$p_val = 0;
# Layer 2: Pointer to Layer 1 (int**)
my $pp_mem = malloc(8);
my $pp_val = Affix::cast( $pp_mem, Pointer [ Pointer [Int] ] );
$$pp_val = $p_val; # Writes address of $p_mem into $pp_mem
# Layer 3: Pointer to Layer 2 (int***)
my $ppp_mem = malloc(8);
my $ppp_val = Affix::cast( $ppp_mem, Pointer [ Pointer [ Pointer [Int] ] ] );
# Dereference to invoke SET magic, writing the pointer address to memory.
$$ppp_val = $pp_val;
# Call Function
$set_deep->( $ppp_val, 12345 );
# Verification
is $$p_val, 12345, '***int deep write successful via Pins';
# Cleanup (Freeing the originals clears the memory)
Affix::free($p_mem);
Affix::free($pp_mem);
Affix::free($ppp_mem);
# Manual Memory Management (malloc/free/cast)
isa_ok my $get_heap = wrap( $lib_path, 'get_heap_int', [Int] => Pointer [Int] ), ['Affix'];
# Alias libc free to avoid conflict with Affix::free
diag affix( $lib_path, 'libc_free', [ Pointer [Void] ] => Void );
( run in 0.648 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )