FFI::P...s::Type(3pm)

๐Ÿ’… FFI::Platypus::Type ๐Ÿ’…

okay bestie, welcome to the FFI::Platypus::Type page โ€” it lives in the User Contributed Perl Documentation section, and its full little name is FFI::Platypus::Type(3pm). This is the doc that teaches you how to define types for FFI::Platypus, and honestly? iconic behavior. ๐Ÿฆ‹

โœจ VERSION โœจ

version 2.11 โ€” that's the whole vibe, no notes. ๐ŸŽ€

๐ŸŒธ SYNOPSIS ๐ŸŒธ

OO Interface, babes:

use FFI::Platypus 2.00;
my $ffi = FFI::Platypus->new( api => 2 );
$ffi->type('int' => 'my_int');

๐Ÿ’– DESCRIPTION ๐Ÿ’–

Okay so first thing, a note: this whole document assumes you're using api => 2, which โ€” and I'm saying this with love โ€” you should be using for all new code. Periodt. ๐Ÿ’…

This doc is all about how to define types with FFI::Platypus. Types can be "defined" ahead of time, or you can just... use them right there when you're defining or attaching functions. Both are valid, both slay.

# Example of defining types
use FFI::Platypus 2.00;
my $ffi = FFI::Platypus->new( api => 2 );
$ffi->type('int');
$ffi->type('string');

# Example of simply using types in function declaration or attachment
my $f = $ffi->function(puts => ['string'] => 'int');
$ffi->attach(puts => ['string'] => 'int');

So, unless you're using aliases (more on those below ๐Ÿ‘€), the FFI::Platypus#type method isn't strictly necessary. But here's the tea: it will throw an exception if the type is incorrectly specified or just not supported โ€” which is genuinely helpful for figuring out whether the types are even available. Like a little vibe check for your types. โœจ

Note: sometimes this doc says "C Function" as shorthand for a function implemented in a compiled language. Unless it's literally talking about a C function in example code, you can assume it works with another compiled language too. We're inclusive here. ๐Ÿซถ

๐Ÿ” meta information about types

You can grab the size of a type with the FFI::Platypus#sizeof method:

my $intsize = $ffi->sizeof('int');           # usually 4
my $intarraysize = $ffi->sizeof('int[64]');  # usually 256

๐Ÿช„ converting types

Sometimes you gotta convert types, bestie. Especially pointer types โ€” those often need converting so Perl can actually consume them. That's what the FFI::Platypus#cast method is for. Use it with care though, because not all type combinations are supported. Here are some useful ones:

my $address = $ffi->cast('string' => 'opaque', $string);

That converts a Perl string into a pointer address that functions taking an opaque type can use. โš ๏ธ But be careful โ€” the Perl string must not be resized or free'd while C code is still using it. Like, do not touch it, it's busy.

my $string  = $ffi->cast('opaque' => 'string', $pointer);

And this does the opposite โ€” takes a null terminated string (the kind C uses) and turns it into a Perl string. In this case the string gets copied, so the other language is free to deallocate or otherwise mess with the string after the conversion without ruining the Perl side. Slay. ๐Ÿ’–

๐Ÿท๏ธ aliases

Sometimes alternate names are just useful for documenting what an argument or return type is actually for. That's where "aliases" come in clutch. The second argument to the FFI::Platypus#type method defines a type alias that you can later use in function declaration and attachment:

use FFI::Platypus 2.00;
my $ffi = FFI::Platypus->new( api => 2 );
$ffi->type('int'    => 'myint');
$ffi->type('string' => 'mystring');
my $f = $ffi->function( puts => ['mystring'] => 'myint' );
$ffi->attach( puts => ['mystring'] => 'myint' );

Aliases are contained within the FFI::Platypus object, so go off โ€” define your own crazy types without stepping on the toes of other CPAN developers using Platypus. Everyone gets their own lane. ๐Ÿฆ‹

One genuinely useful application of an alias is when you know the types are different on two different platforms:

if($^O eq 'MSWin32')
{
  $type->type('sint16' => 'foo_t');
} elsif($^O eq 'linux')
{
  $type->type('sint32' => 'foo_t');
}

# function foo takes 16 bit signed integer on Windows
# and a 32 bit signed integer on Linux.
$ffi->attach( foo => [ 'foo_t' ] => 'void' );

๐Ÿ‘‘ TYPE CATEGORIES ๐Ÿ‘‘

๐ŸŒˆ Native types

So-called native types are the types the CPU actually understands โ€” the ones that can be passed on the argument stack or returned by a function. They do not include more complicated things like arrays or structs, which get passed via pointers (peep the opaque type below). Generally, native types are void, integers, floats and pointers. The classics. ๐Ÿ’…

๐Ÿซง the void type

You use this as a return value to say a function doesn't return anything (or when you just want the return value ignored):

$ffi->type( foo => [] => 'void' );

Newer versions of Platypus also let you just omit the return type, and void is assumed:

$ffi->type( foo => [] );

It doesn't really make sense to use void in any other context. However, because of historical reasons involving older versions of Perl. (yeah, the sentence just ends there, bestie โ€” I don't make the rules ๐Ÿ’€)

It also doesn't really make sense for void to be passed in as an argument. But since C functions that take no arguments are frequently specified as taking void (older C compilers required it), there's a special case: you can specify a function's arguments as a single void to mean it takes no arguments. Cute. ๐ŸŽ€

# C: void foo(void);
$ffi->type( foo => ['void'] );
# same (but probably better)
$ffi->type( foo => [] );

๐Ÿ”ข integer types

These native integer types are always available (the parentheticals show the usual corresponding C type):

  • sint8 โ€” signed 8 bit byte (signed char, int8_t).
  • uint8 โ€” unsigned 8 bit byte (unsigned char, uint8_t).
  • sint16 โ€” signed 16 bit integer (short, int16_t)
  • uint16 โ€” unsigned 16 bit integer (unsigned short, uint16_t)
  • sint32 โ€” signed 32 bit integer (int, int32_t)
  • uint32 โ€” unsigned 32 bit integer (unsigned int, uint32_t)
  • sint64 โ€” signed 64 bit integer (long long, int64_t)
  • uint64 โ€” unsigned 64 bit integer (unsigned long long, uint64_t)

You can also use uchar, ushort, uint and ulong as short names for unsigned char, unsigned short, unsigned int and unsigned long. Short and sweet, we love. ๐Ÿ“

These integer types are also available, but their actual size and sign may depend on the platform โ€” so like, read the room (and the platform):

  • char โ€” somewhat confusingly, char is an integer type! It's really an alias for either sint8_t or uint8_t depending on your platform. If you want to pass a character (not an integer) into a C function that takes a character, you want the perl ord function. Here's an example using the standard libc isalpha, isdigit type functions:
use FFI::Platypus 2.00;

my $ffi = FFI::Platypus->new( api => 2 );
$ffi->lib(undef);
$ffi->type('int' => 'character');

my @list = qw(
  alnum alpha ascii blank cntrl digit lower print punct
  space upper xdigit
);

$ffi->attach("is$_" => ['character'] => 'int') for @list;

my $char = shift(@ARGV) || 'a';

no strict 'refs';
printf "'%s' is %s %s\n", $char, $_, &{'is'.$_}(ord $char) for @list;
  • size_t โ€” this is usually an unsigned long, but honestly it's up to the compiler to decide. The malloc function is defined in terms of size_t:
$ffi->attach( malloc => ['size_t'] => 'opaque';

(fun fact: note that you can get malloc from FFI::Platypus::Memory. also yes, that paren is missing in the original โ€” iykyk ๐Ÿ˜ญ)

  • long, unsigned long โ€” on 64 bit systems, this is usually a 64 bit integer. On 32 bit systems this is frequently a 32 bit integer (and long long or unsigned long long are for 64 bit).

There are a bunch of other types that may or may not be available if they got detected when FFI::Platypus was installed. That includes things like wchar_t, off_t, wint_t. You can use this script to list all the integer types FFI::Platypus knows about, plus how they're implemented:

use FFI::Platypus 2.00;

my $ffi = FFI::Platypus->new( api => 2 );

foreach my $type_name (sort $ffi->types)
{
  my $meta = $ffi->type_meta($type_name);
  next unless defined $meta->{element_type} && $meta->{element_type} eq 'int';
  printf "%20s %s\n", $type_name, $meta->{ffi_type};
}

If you need a common system type that isn't provided, please open a ticket in the Platypus project's GitHub issue tracker. And be sure to include the usual header file the type can be found in. ๐Ÿ’œ

๐Ÿงš Enum types

C provides enumerated types, which are typically implemented as integer types:

enum {
  BAR = 1,
  BAZ = 2
} foo_t;

void f(enum foo_t foo);

Platypus gives you enum and senum types for the integer types used to represent enum and signed enum types respectively:

use constant BAR => 1;
use constant BAZ => 2;
$ffi->attach( f => [ 'enum' ] => 'void' );
f(BAR);
f(BAZ);

So when do you use senum? Anytime the enum has negative values:

enum {
  BAR = -1;
  BAZ = 2;
} foo_t;

void f(enum foo_t foo);

Perl:

use constant BAR => -1;
use constant BAZ => 2;
$ffi->attach( f => [ 'senum' ] => 'void' );
f(BAR);
f(BAZ);

Dealing with enumerated values with FFI can be tricky, ngl, because they're usually defined in C header files and can't be found in dynamic libraries. For trivial usage you can do what's illustrated above โ€” just define your own Perl constants. For more complicated usage, or where the values might vary from platform to platform, you may want to consider the new Platypus bundle interface to define Perl constants (essentially the same as an enumerated value) from C space. It's more reliable, but it does require a compiler at install time. See FFI::Platypus::Constant for details. ๐Ÿ”ฅ

The main FAQ (FAQ in FFI::Platypus) also has a discussion on dealing with constants and enumerated types, so go read that if you're curious.

There's also a type plugin (FFI::Platypus::Type::Enum) that can be helpful when you're writing interfaces that use enums. ๐Ÿฆ‹

๐ŸŒธ Boolean types

At install time Platypus attempts to detect the correct type for bool on your platform, and you can use it. bool is really an integer type, but the exact type used varies from platform to platform. So it's giving "technically an int, spiritually a vibe."

C header:

#include <stdbool.h>
bool foo();

Platypus:

$ffi->attach( foo => [] => 'bool' );

If you get an exception when trying to use this type, it means either you have a very old version of Platypus, or for some reason it couldn't detect the correct type at install time. Please open a ticket if that's the case. ๐Ÿ™ˆ

โญ floating point types

These native floating point types are always available (parentheticals indicate the usual corresponding C type):

  • float โ€” single precision floating point (float)
  • double โ€” double precision floating point (double)

๐Ÿฆ‹ longdouble

So longdouble is floating point that might be bigger than a plain double (that's the longdouble situation in C). It's only available if the C compiler used to build FFI::Platypus supports it โ€” no compiler support, no type, periodt. Also, there may be a performance penalty for using this type even if your Perl uses long doubles internally for its number value (NV) type, and that's lowkey because of the way FFI::Platypus interacts with libffi. Not us, not you, just the plumbing ๐Ÿ’€

When it's an argument type, you can pass in either regular number values (NV) or instances of Math::LongDouble โ€” both are accepted, pick your fighter. When it's used as a return type, Math::LongDouble will be used if you have that module installed; otherwise the return type gets downgraded to whatever your Perl's number value (NV) is. ๐Ÿ’…

๐ŸŒธ complex_float and complex_double

  • complex_float โ€” complex single precision floating point (float complex)
  • complex_double โ€” complex double precision floating point (double complex)

Both of these are only available if supported by your C compiler and by libffi. Complex numbers are only supported in very recent versions of libffi, and as of this writing the latest production version doesn't work on x86_64 ๐Ÿ˜ญ. It does seem to work with the latest production version of libffi on 32 bit Intel (x86), and with the latest libffi version in git on x86_64. Vibe check passed, but only on some architectures, bestie.


๐Ÿ‘‘ opaque pointers

Opaque pointers are literally just a pointer to a region of memory that you do not manage, and you don't know or care about its structure. It's like a void * in C โ€” main character energy, no internals needed. These types are represented in Perl space as integers and get converted to and from pointers by FFI::Platypus. You may use pointer as an alias for opaque, although that's discouraged ๐Ÿ™ˆ (the Platypus documentation uses the convention of using pointer to refer to pointers to known types โ€” see below โ€” and opaque as short hand for opaque pointer).

As an example, libarchive defines struct archive type in its header files, but does not define its content. Internally it is defined as a struct type, but the caller does not see this. It is therefore opaque to its caller. There are archive_read_new and archive_write_new functions to create a new instance of this opaque object and archive_read_free and archive_write_free to destroy this objects when you are done.

C header:

struct archive;
struct archive *archive_read_new(void);
struct archive *archive_write_new(void);
int archive_free(struct archive *);
int archive_write_free(struct archive *);

Perl code:

$lib->find_lib( lib => 'archive' );
$ffi->attach(archive_read_new   => []         => 'opaque');
$ffi->attach(archive_write_new  => []         => 'opaque');
$ffi->attach(archive_read_free  => ['opaque'] => 'int');
$ffi->attach(archive_write_free => ['opaque'] => 'int');

It is often useful to alias an opaque type like this so that you know what the object represents:

$lib->find_lib( lib => 'archive' );
$ffi->type('opaque' => 'archive');
$ffi->attach(archive_read_new   => [] => 'archive');
$ffi->attach(archive_read_free  => ['archive'] => 'int');
...

As a special case โœจ when you pass undef into a function that takes an opaque type it will be translated into NULL for C. And when a C function returns a NULL pointer, it will be translated back to undef. Symmetry, we love to see it.

For functions that take a pointer to a void pointer (that is a void **), you can use a pointer to an opaque type. Consider the C code:

struct archive_entry;
int archive_read_next_header(struct archive *, struvct archive_entry **);

Once again the internals of archive_entry are not provided. Perl code:

$ffi->type('opaque' => 'archive_entry');
$ffi->attach(archive_read_next_header => [ 'archive', 'archive_entry*' ] => 'int');

Now we can call this function:

my $archive = archive_read_new();
...  # additional prep for $active is required
while(1) {
  my $entry;
  archive_read_next_header($archive, \$entry);
  last unless defined $entry;
  # can now use $entry for other archive_entry_ methods.
}

The way archive_read_next_header works, it will return a pointer to the next archive_entry object until it gets to the end, when it will return a pointer to NULL which will be represented in Perl by an undef. Chef's kiss ๐ŸŽ€

There are a number of useful utility functions for dealing with opaque types in the FFI::Platypus::Memory module โ€” go look, iykyk.


๐Ÿ’œ Objects

Object types are thin wrappers around two native types: integer and opaque types. They're just blessed references around either of those two types so that methods can be defined on them, but when they get passed to a Platypus xsub they are converted into the native integer or opaque types. This type is most useful when an API provides an OO style interface with an integer or opaque value acting as an instance of a class. There are two detailed examples in the main Platypus documentation using libarchive and unix open:

  • "libarchive" in FFI::Platypus
  • "unix open" in FFI::Platypus

๐Ÿงš Strings

# used when you need a char * or const char *
$ffi->attach( puts => [ 'string' ] => 'int' );

The string type is a series of bytes that usually represent a series of characters. They will be NULL terminated for C and passed in as a pointer. This will typically work for APIs that take ASCII or UTF-8 strings, which are common in Unix environments. Very much the standard, no notes ๐Ÿ’…

(Note if you need to handle the native "wide" string โ€” for example if you need to talk UTF-16 on Windows โ€” see FFI::Platypus::Type::WideString.)

(Note if you need to pass in a fixed length string by value, not as a pointer, then you can do so using FFI::Platypus::Record.)

(Note that languages like Go and Rust do not use NULL terminated strings and need their own string types; see the appropriate language plugins for details.)

# can also be used when you need a void * or const void *
$ffi->attach( write => ['int', 'string', 'size_t' ] => 'ssizet' );

The string type can also be used to pass in the start of a buffer of arbitrary bytes stored in a Perl scalar. Because a string is passed just as a pointer you will typically need to also pass the length of the buffer as a separate argument โ€” this is necessary because buffers could potentially have a NULL in them. Yes, actually necessary, not just being extra.

The pointer passed into C (or another language) is to the content of the actual scalar, which means it can modify the content of a scalar. So like, it's not a copy. Handle with care ๐Ÿซถ

NOTE: When used as a return type, the string is copied into a new scalar rather than using the original address. This is due to the ownership model of scalars in Perl, but it is also most of the time what you want.

This can be problematic when a function returns a string that the callee is expected to free. Consider the functions:

char *
get_string()
{
  char *buffer;
  buffer = malloc(20);
  strcpy(buffer, "Perl");
}

void
free_string(char *buffer)
{
  free(buffer);
}

This API returns a string that you are expected to free when you are done with it. (At least they have provided an API for freeing the string instead of expecting you to call libc free!) A simple binding to get the string would be:

$ffi->attach( get_string => [] => 'string' );  # memory leak
my $str = get_string();

Which will work to a point, but the memory allocated by get_string will leak. Not cute. Instead you need to get the opaque pointer, cast it to a string and then free it:

$ffi->attach( get_string => [] => 'opaque' );
$ffi->attach( free_string => ['opaque'] => 'void' );
my $ptr = get_string();
my $str = $ffi->cast( 'opaque' => 'string', $ptr );  # copies the string
free_string($ptr);

If you are doing this sort of thing a lot, it can be worth adding a custom type:

$ffi->attach( free_string => ['opaque'] => 'void' );
$ffi->custom_type( 'my_string' => {
  native_type => 'opaque',
  native_to_perl => sub {
    my($ptr) = @_;
    my $str = $ffi->cast( 'opaque' => 'string', $ptr ); # copies the string
    free_string($ptr);
    $str;
  }
});

$ffi->attach( get_string => [] => 'my_string' );
my $str = get_string();

Since version 0.62, pointers and arrays to strings are supported as a first class type. Prior to that FFI::Platypus::Type::StringArray and FFI::Platypus::Type::StringPointer could be used, though their use in new code is discouraged. We moved on, bestie ๐Ÿ’…

$ffi->attach( foo => ['string[]'] => 'void' );
foo( [ 'array', 'of', 'strings' ] );

$ffi->attach( bar => ['string*'] => 'void' );
my $string = 'baz';
bar( \$string );  # $string may be modified.

Strings are not allowed as return types from closure ๐Ÿ˜ญ. This, again, is due to the ownership model of scalars in Perl. (There is no way for Perl to know when the calling language is done with the memory allocated to the string.) Consider the API:

typedef const char *(*get_message_t)(void);

void
print_message(get_message_t get_message)
{
  const char *str;
  str = get_message();
  printf("message = %s\n", str);
}

It feels like this should be able to work:

$ffi->type('()->string' => 'get_message_t'); # not ok
$ffi->attach( print_message => ['get_message_t'] => 'void' );
my $get_message = $ffi->closure(sub {
  return "my message";
});
print_message($get_message);

If the type declaration for get_message_t were legal, then this script would likely segfault or in the very least corrupt memory ๐Ÿ’€. The problem is that once "my message" is returned from the closure Perl doesn't have a reference to it anymore and will free it. To do this safely, you have to keep a reference to the scalar around and return an opaque pointer to the string using a cast:

$ffi->type('()->opaque' => 'get_message_t');
$ffi->attach( print_message => ['get_message_t'] => 'void' );
my $get_message => $ffi->closure(sub {
  our $message = "my message";  # needs to be our so that it doesn't
                                # get free'd
  my $ptr = $ffi->cast('string' => 'opaque', $message);
  return $ptr;
});
print_message($get_message);

Another type of string that you may run into with some APIs is the so called "wide" string ๐ŸŒˆ. In your C code if you see wchar_t* or const wchar_t*, or if in Win32 API code you see LPWSTR or LPCWSTR โ€” those. Most commonly you will see these types when working with the Win32 API, but you may see them in Unix as well. These types are intended for dealing with Unicode, but they do not use the same UTF-8 format used by Perl internally, so they need to be converted. You can do this manually by allocating the memory and using the Encode module, but the easier way is to use either FFI::Platypus::Type::WideString or FFI::Platypus::Lang::Win32, which handle the memory allocation and conversion for you. Obsessed with a plugin that does the work for you, honestly ๐Ÿซถ

okay so string types can be fixed-length โ€” you literally just slap a trailing parenthetical on the end like string(10) and that's it ๐Ÿ’… for arguments this has, like, basically no practical effect, because strings get passed around as pointers anyway. but it DOES matter for return values, and that's the part you gotta clock: if a function whose return type is string(10) hands back a string that is not NULL terminated, then only the first ten bytes come back to you in the result. ten bytes. that's the whole vibe. ๐Ÿ”ฅ

internally, fixed length strings are implemented exactly the same way as classless record types โ€” like, string(10) is identically internally the same as "record(10)*". no cap.

for the 1.00 Platypus API, the string(10) type was specified as a pointer, as in "string(10)*". that was a mistake ๐Ÿ™ˆ but bestie you can still use the latter as an alias for the correct form in the 2.00 API. so no panic, it still works.


๐ŸŽ€ Pointers and Arrays of Strings ๐ŸŽ€

as of the 1.00 Platypus API, you can specify pointers to strings (string*) and arrays of strings (string[10]). and because strings themselves are already passed as pointers, this means these types get passed in as pointers to pointers. so if the pointer to the string gets changed, then when the function returns the scalar or array will be updated too. slay. โœจ

๐Ÿฆ‹ Pointer / References ๐Ÿฆ‹

in C you can pass a pointer to a variable into a function to accomplish that whole pass-by-reference thing. in Perl the same task is accomplished by passing a reference (though you can also modify the argument stack, which is why Perl supports proper pass by reference as well, iykyk).

with FFI::Platypus you can define a pointer to any native, string or record type. you cannot (at least not yet) define a pointer to a pointer, or a pointer to an array, or any other type that isn't otherwise supported. when you pass in a pointer to something you must make sure you pass in a reference to a scalar, or undef (undef gets translated into NULL, iconic behaviour honestly).

if the C code makes a change to the value pointed to by the pointer, the scalar will be updated before returning to Perl space. example time, with C code:

/* foo.c */
void increment_int(int *value)
{
  if(value != NULL)
    (*value)++;
  else
    fprintf(stderr, "NULL pointer!\n");
}
# foo.pl
use FFI::Platypus 2.00;
my $ffi = FFI::Platypus->new( api => 2 );
$ffi->lib('libfoo.so'); # change to reflect the dynamic lib
                        # that contains foo.c
$ffi->type('int*' => 'int_p');
$ffi->attach(increment_int => ['int_p'] => 'void');
my $i = 0;
increment_int(\$i);   # $i == 1
increment_int(\$i);   # $i == 2
increment_int(\$i);   # $i == 3
increment_int(undef); # prints "NULL pointer!\n"

older versions of Platypus did not support pointers to strings or records btw. vintage behaviour. ๐Ÿซถ


๐Ÿ‘‘ Records ๐Ÿ‘‘

records are structured data of a fixed length. in C they're called structs, and yes that's the whole name, C people were just like "struct. done." ๐Ÿ’€

for most C structured data, as long as you don't need to pass a record by value, FFI::C is the better choice. quick rundown: FFI::C supports struct, union, and arrays of struct and unions. FFI::C does not support passing by value. the rest of this section is only gonna talk about the record type, so heads up.

to declare a record type, use record:

$ffi->type( 'record (42)' => 'my_record_of_size_42_bytes' );

the easiest way to manage records with Platypus is by using FFI::Platypus::Record to define a record layout for a record class. here's a brief little example, and it ate, honestly:

package Unix::TimeStruct;

use FFI::Platypus 2.00;
use FFI::Platypus::Record;

record_layout_1(qw(
    int    tm_sec
    int    tm_min
    int    tm_hour
    int    tm_mday
    int    tm_mon
    int    tm_year
    int    tm_wday
    int    tm_yday
    int    tm_isdst
    long   tm_gmtoff
    string tm_zone
));

my $ffi = FFI::Platypus->new( api => 2 );
$ffi->lib(undef);
# define a record class Unix::TimeStruct and alias it to "tm"
$ffi->type("record(Unix::TimeStruct)*" => 'tm');

# attach the C localtime function as a constructor
$ffi->attach( localtime => ['time_t*'] => 'tm', sub {
  my($inner, $class, $time) = @_;
  $time = time unless defined $time;
  $inner->(\$time);
});

package main;

# now we can actually use our Unix::TimeStruct class
my $time = Unix::TimeStruct->localtime;
printf "time is %d:%d:%d %s\n",
  $time->tm_hour,
  $time->tm_min,
  $time->tm_sec,
  $time->tm_zone;

for more detailed usage, go check out FFI::Platypus::Record ๐Ÿซก

okay so Platypus does not manage the structure of a record (that part is up to you, bestie) โ€” it just keeps track of their size and makes sure they're copied correctly when they're used as a return type. a record in Perl is literally just a string of bytes stored as a scalar. and on top of defining a record layout for a record class, there are a bunch of tools you can use to manipulate records in Perl โ€” two notable examples are pack and unpack and Convert::Binary::C. ๐ŸŒธ

here's an example with commentary that uses Convert::Binary::C to extract the component time values from the C localtime function, and then smushes them back together to get the original time_t (which is an integer):

use Convert::Binary::C;
use FFI::Platypus 2.00;
use Data::Dumper qw( Dumper );

my $c = Convert::Binary::C->new;

# Alignment of zero (0) means use
# the alignment of your CPU
$c->configure( Alignment => 0 );

# parse the tm record structure so
# that Convert::Binary::C knows
# what to spit out and suck in
$c->parse(<<ENDC);
struct tm {
  int tm_sec;
  int tm_min;
  int tm_hour;
  int tm_mday;
  int tm_mon;
  int tm_year;
  int tm_wday;
  int tm_yday;
  int tm_isdst;
  long int tm_gmtoff;
  const char *tm_zone;
};
ENDC

# get the size of tm so that we can give it
# to Platypus
my $tm_size = $c->sizeof("tm");

# create the Platypus instance and create the appropriate
# types and functions
my $ffi = FFI::Platypus->new( api => 2 );
$ffi->lib(undef);
$ffi->type("record($tm_size)*" => 'tm');
$ffi->attach( [ localtime => 'my_localtime' ] => ['time_t*'] => 'tm'     );
$ffi->attach( [ time      => 'my_time'      ] => ['tm']      => 'time_t' );

# ===============================================
# get the tm struct from the C localtime function
# note that we pass in a reference to the value that time
# returns because localtime takes a pointer to time_t
# for some reason.
my $time_hashref = $c->unpack( tm => my_localtime(\time) );

# tm_zone comes back from Convert::Binary::C as an opaque,
# cast it into a string.  We localize it to just this do
# block so that it will be a pointer when we pass it back
# to C land below.
do {
  local $time_hashref->{tm_zone} = $ffi->cast(opaque => string => $time_hashref->{tm_zone});
  print Dumper($time_hashref);
};

# ===============================================
# convert the tm struct back into an epoch value
my $time = my_time( $c->pack( tm => $time_hashref ) );

print "time      = $time\n";
print "perl time = ", time, "\n";

you can also link a record type to a class. it'll then be accepted when it's blessed into that class as an argument passed into a C function, and when it gets returned from a C function it'll be blessed into that class. basically:

$ffi->type( 'record(My::Class)*' => 'my_class' );
$ffi->attach( my_function1 => [ 'my_class' ] => 'void' );
$ffi->attach( my_function2 => [ ] => 'my_class' );

the ONLY thing your class MUST provide is either a ffi_record_size or _ffi_record_size class method that returns the size of the record in bytes. that's the bare minimum, the whole entire vibe check. ๐Ÿ‘‘

now, contrast a record type โ€” stored as a scalar string of bytes in Perl โ€” with an opaque pointer, which is stored as an integer in Perl. both of them get treated as pointers in C functions. the situations when you usually want to use a record are when you know ahead of time what the size of the object you're working with is, and probably something about its structure too. and because a function that returns a structure copies the structure into a Perl data structure, you wanna make sure it's okay to copy the record objects you're dealing with if any of your functions will be returning one of them. ๐Ÿ˜ญ

opaque pointers should be used when you do not know the size of the object you're using, or if the objects are created and free'd through an API interface other than malloc and free.

the examples in this section actually use pointers to records (note the trailing star * in the declarations). most programming languages let you pass or return a record as either pass-by-value or as a pointer (pass-by-reference), so, like, both flavours exist:

C code:

struct { int a; } foo_t;
void pass_by_value_example( struct foo_t foo );
void pass_by_reference_example( struct foo_t *foo );

Perl code:

{
  package Foo;
  use FFI::Platypus::Record;
  record_layout_1( int => 'a' );
}
$ffi->type( 'Record(Foo)' => 'foo_t' );
$ffi->attach( pass_by_value_example => [ 'foo_t' ] => 'void' );
$ffi->attach( pass_by_reference_example => [ 'foo_t*' ] => 'void' );

as with strings, functions that return a pointer to a record are actually copied. yes, copied. the data gets duplicated, that's just how it is:

C code:

struct foo_t *return_struct_pointer_example();

Perl code:

$ffi->attach( return_struct_pointer_example => [] => 'foo_t*' );
my $foo = return_struct_pointer_example();
# $foo is a copy of the record returned by the function.

and also as with strings, if the API expects you to free the record it returns (it's misbehaving a little, but lets set that aside), then you can work around this by returning an opaque type, casting to the record, and finally freeing the original pointer. workaround queen behaviour โœจ

use FFI::Platypus::Memory qw( free );
$ffi->attach( return_struct_pointer_example => [] => 'opaque' );
my $foo_ptr = return_struct_pointer_example();
my $foo = $ffi->cast( 'opaque' => 'foo_t*', $foo_ptr );
free $foo_ptr;

you can pass records into a closure, but care needs to be taken bestie. records passed into a closure are read-only inside the closure, including string rw members. and even though you can pass a pointer to a record into a closure, because of limitations of the implementation you actually have a copy โ€” so all records passed into closures are passed by-value. periodt.

final note, and it's a cute one: a record that does not have a class (so, classless) and is defined using a length instead is internally identical to fixed strings. that is to say string(10) and record(10)* are identical. same thing. twins. ๐Ÿซถ

๐Ÿงฎ Fixed length arrays

okay so fixed length arrays of native types and strings? fully supported by FFI::Platypus, bestie. ๐Ÿ’… And like pointers, if the values contained in the array get updated by the C function, those changes are reflected when it returns to Perl space. Live-updating, iconic behavior honestly.

An example of using this is the Unix pipe command which returns a list of two file descriptors as an array.

use FFI::Platypus 2.00;

my $ffi = FFI::Platypus->new( api => 2 );
$ffi->lib(undef);
$ffi->attach([pipe=>'mypipe'] => ['int[2]'] => 'int');

my @fd = (0,0);
mypipe(\@fd);
my($fd1,$fd2) = @fd;

print "$fd1 $fd2\n";

Because of the way records are implemented, an array of records does not make sense and is not currently supported. ๐Ÿ’€ that's just the rules, periodt.


๐Ÿ“ Variable length arrays

[version 0.22] โ€” vintage, but still slaying.

Variable length arrays are supported for argument types, and you can also specify them using the [] notation but by leaving the size empty:

$ffi->type('int[]' => 'var_int_array');

When you use it as an argument type it will probe the array reference that you pass in to determine the correct size. ๐Ÿ” Usually you'll need to communicate the size of the array to the C code though. One way to do this is to pass the length of the array in as an additional argument. For example the C code:

int
sum(int *array, int size)
{
  int total, i;
  for (i = 0, total = 0; i < size; i++)
  {
    total += array[i];
  }
  return total;
}

Can be called from Perl like this:

use FFI::Platypus 2.00;

my $ffi = FFI::Platypus->new( api => 2 );
$ffi->lib('./var_array.so');

$ffi->attach( sum => [ 'int[]', 'int' ] => 'int' );

my @list = (1..100);

print sum(\@list, scalar @list), "\n";

Another method might be to have a special value, such as 0 or NULL indicate the termination of the array. ๐Ÿ›‘

Because of the way records are implemented, an array of records does not make sense and is not currently supported. (yes, again. it's a whole thing.)


๐ŸŽ€ Closures

A closure (sometimes called a "callback", we use the libffi terminology) is a Perl subroutine that can be called from C. In order to be called from C it needs to be passed to a C function. To define the closure type you need to provide a list of argument types and a return type.

Currently only native types (integers, floating point values, opaque), strings and records (by-value; you can pass a pointer to a record, but due to limitations of the record implementation this is actually a copy) are supported as closure argument types, and only native types and records (by-value; pointer records and records with string pointers cannot be returned from a closure) are supported as closure return types. Inside the closure any records passed in are read-only. ๐Ÿ™ˆ

We plan to add other types, though they can be converted using the Platypus cast or attach_cast methods.

Here is an example, with C code:

/*
 * closure.c - on Linux compile with: gcc closure.c -shared -o closure.so -fPIC
 */

#include <stdio.h>

typedef int (*closure_t)(int);
closure_t my_closure = NULL;

void set_closure(closure_t value)
{
  my_closure = value;
}

int call_closure(int value)
{
  if(my_closure != NULL)
    return my_closure(value);
  else
    fprintf(stderr, "closure is NULL\n");
}

And the Perl code:

use FFI::Platypus 2.00;

my $ffi = FFI::Platypus->new( api => 2 );
$ffi->lib('./closure.so');
$ffi->type('(int)->int' => 'closure_t');

$ffi->attach(set_closure => ['closure_t'] => 'void');
$ffi->attach(call_closure => ['int'] => 'int');

my $closure1 = $ffi->closure(sub { $_[0] * 2 });
set_closure($closure1);
print  call_closure(2), "\n"; # prints "4"

my $closure2 = $ffi->closure(sub { $_[0] * 4 });
set_closure($closure2);
print call_closure(2), "\n"; # prints "8"

If you have a pointer to a function in the form of an opaque type, you can pass this in place of a closure type:

use FFI::Platypus 2.00;

my $ffi = FFI::Platypus->new( api => 2 );
$ffi->lib('./closure.so');
$ffi->type('(int)->int' => 'closure_t');

$ffi->attach(set_closure => ['closure_t'] => 'void');
$ffi->attach(call_closure => ['int'] => 'int');

my $closure = $ffi->closure(sub { $_[0] * 6 });
my $opaque = $ffi->cast(closure_t => 'opaque', $closure);
set_closure($opaque);
print call_closure(2), "\n"; # prints "12"

The syntax for specifying a closure type is a list of comma separated types in parentheticals followed by a narrow arrow ->, followed by the return type for the closure. For example a closure that takes a pointer, an integer and a string and returns an integer would look like this:

$ffi->type('(opaque, int, string) -> int' => 'my_closure_type');
โš ๏ธ Care needs to be taken with scoping and closures, because of the way Perl and C handle responsibility for allocating memory differently. Perl keeps reference counts and frees objects when nothing is referencing them. In C the code that allocates the memory is considered responsible for explicitly free'ing the memory for objects it has created when they are no longer needed. When you pass a closure into a C function, the C code has a pointer or reference to that object, but it has no way up letting Perl know when it is no longer using it. As a result, if you do not keep a reference to your closure around it will be free'd by Perl and if the C code ever tries to call the closure it will probably SIGSEGV. ๐Ÿ˜ญ

Thus supposing you have a C function set_closure that takes a Perl closure, this is almost always wrong:

set_closure($ffi->closure({ $_[0] * 2 }));  # BAD

In some cases, you may want to create a closure shouldn't ever be free'd. For example you are passing a closure into a C function that will retain it for the lifetime of your application. You can use the sticky method to keep the closure, without the need to keep a reference of the closure:

{
  my $closure = $ffi->closure(sub { $_[0] * 2 });
  $closure->sticky;
  set_closure($closure); # OKAY
}
# closure still exists and is accesible from C, but
# not from Perl land.

๐Ÿ’Ž Custom Types

Custom Types in Perl ๐Ÿซถ

Platypus custom types are the rough analogue to typemaps in the XS world. They offer a method for converting Perl types into native types that the libffi can understand and pass on to the C code.

โœจ Example 1: Integer constants

Say you have a C header file like this:

/* possible foo types: */
#define FOO_STATIC  1
#define FOO_DYNAMIC 2
#define FOO_OTHER   3

typedef int foo_t;

void foo(foo_t foo);
foo_t get_foo();

The challenge is here that once the source is processed by the C pre-processor the name/value mappings for these FOO_ constants are lost. There is no way to fetch them from the library once it is compiled and linked. ๐Ÿซ 

One common way of implementing this would be to create and export constants in your Perl module, like this:

package Foo;

use FFI::Platypus 2.00;
use Exporter qw( import );

our @EXPORT_OK = qw( FOO_STATIC FOO_DYNAMIC FOO_OTHER foo get_foo );

use constant FOO_STATIC  => 1;
use constant FOO_DYNAMIC => 2;
use constant FOO_OTHER   => 3;

my $ffi = FFI::Platypus->new( api => 2 );
$ffi->attach(foo     => ['int'] => 'void');
$ffi->attach(get_foo => []      => 'int');

Then you could use the module thus:

use Foo qw( foo FOO_STATIC );
foo(FOO_STATIC);

If you didn't want to rely on integer constants or exports, you could also define a custom type, and allow strings to be passed into your function, like this:

package Foo;

use FFI::Platypus 2.00;

our @EXPORT_OK = qw( foo get_foo );

my %foo_types = (
  static  => 1,
  dynamic => 2,
  other   => 3,
);
my %foo_types_reverse = reverse %foo_types;

my $ffi = FFI::Platypus->new( api => 2 );
$ffi->custom_type(foo_t => {
  native_type    => 'int',
  native_to_perl => sub {
    $foo_types{$_[0]};
  },
  perl_to_native => sub {
    $foo_types_reverse{$_[0]};
  },
});

$ffi->attach(foo     => ['foo_t'] => 'void');
$ffi->attach(get_foo => []        => 'foo_t');

Now when an argument of type foo_t is called for it will be converted from an appropriate string representation, and any function that returns a foo_t type will return a string instead of the integer representation:

use Foo;
foo('static');

If the library that you are using has a lot of these constants you can try using Convert::Binary::C or another C header parser to obtain the appropriate name/value pairings for the constants that you need. ๐Ÿ”ฅ

๐ŸŒธ Example 2: Blessed references

Supposing you have a C library that uses an opaque pointer with a pseudo OO interface, like this:

typedef struct foo_t;

foo_t *foo_new();
void foo_method(foo_t *, int argument);
void foo_free(foo_t *);

One approach to adapting this to Perl would be to create a OO Perl interface like this:

package Foo;

use FFI::Platypus 2.00;
use FFI::Platypus::API qw( arguments_get_string );

my $ffi = FFI::Platypus->new( api => 2 );
$ffi->custom_type(foo_t => {
  native_type    => 'opaque',
  native_to_perl => sub {
    my $class = arguments_get_string(0);
    bless \$_[0], $class;
  }
  perl_to_native => sub { ${$_[0]} },
});

$ffi->attach([ foo_new => 'new' ] => [ 'string' ] => 'foo_t' );
$ffi->attach([ foo_method => 'method' ] => [ 'foo_t', 'int' ] => 'void');
$ffi->attach([ foo_free => 'DESTROY' ] => [ 'foo_t' ] => 'void');

my $foo = Foo->new;

Here we are blessing a reference to the opaque pointer when we return the custom type for foo_t, and dereferencing that reference before we pass it back in. The function arguments_get_string queries the C arguments to get the class name to make sure the object is blessed into the correct class (for more details on the custom type API see FFI::Platypus::API), so you can inherit and extend this class like a normal Perl class. ๐Ÿ‘‘

This works because the C "constructor" ignores the class name that we pass in as the first argument. If you have a C "constructor" like this that takes arguments you'd have to write a wrapper for new.

A good example of a C library that uses this pattern, including inheritance is libarchive. Platypus comes with a more extensive example in examples/archive.pl that demonstrates this. ๐Ÿฆ‹

๐ŸŽ€ Example 3: Pointers with pack / unpack

TODO

See example FFI::Platypus::Type::StringPointer.

๐Ÿงš Example 4: Custom Type modules and the Custom Type API

TODO

See example FFI::Platypus::Type::PointerSizeBuffer.

โญ Example 5: Custom Type on CPAN

okay so, you can totally distribute your own Platypus custom types on CPAN if you think they might be useful to other people ๐Ÿ’–. The default namespace is prefix with "FFI::Platypus::Type::", though you can stick it anywhere โ€” and honestly, putting it under your own namespace may make more sense if the custom type is specific to your application. Slay, boundaries, etc. โœจ

A good example and pattern to follow is FFI::Platypus::Type::StringArray โ€” iconic behavior, fr fr ๐ŸŽ€.


๐Ÿ’… SEE ALSO ๐Ÿ’…

  • FFI::Platypus โ€” the main platypus documentation, your home base ๐Ÿฆ‹
  • FFI::Platypus::API โ€” the custom types API, for when you're getting fancy ๐Ÿ”ฅ
  • FFI::Platypus::Type::StringPointer โ€” the string pointer type, bestie-coded ๐Ÿ’œ

๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

Author: Graham Ollis <plicease@cpan.org> ๐Ÿ’Œ

Contributors:

  • Bakkiaraj Murugesan (bakkiaraj)
  • Dylan Cali (calid)
  • pipcet
  • Zaki Mughal (zmughal)
  • Fitz Elliott (felliott)
  • Vickenty Fesunov (vyf)
  • Gregor Herrmann (gregoa)
  • Shlomi Fish (shlomif)
  • Damyan Ivanov
  • Ilya Pavlov (Ilya33)
  • Petr Pรญsaล™ (ppisar)
  • Mohammad S Anwar (MANWAR)
  • Hรฅkon Hรฆgland (hakonhagland, HAKONH)
  • Meredith (merrilymeredith, MHOWARD)
  • Diab Jerius (DJERIUS)
  • Eric Brine (IKEGAMI)
  • szTheory
  • Josรฉ Joaquรญn Atria (JJATRIA)
  • Pete Houston (openstrike, HOUSTON)
  • Lukas Mai (MAUKE)

This software is copyright (c) 2015-2022 by Graham Ollis ๐ŸŒธ.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Periodt ๐Ÿ’….


โœจ and that's the whole vibe, bestie: this page is from perl v5.42.3, dated 2026-08-20, and it's the FFI::P...s::Type(3pm) manpage โ€” go forth and type safely ๐Ÿซถ๐Ÿ’–

๐Ÿ“ฆ where this page comes from