Peta::FFI#

📦 std

Call C library functions from Perl with no build step and no binding crate.

Peta::FFI is the dynamic FFI layer pperl ships for reaching into shared libraries at runtime. You open a library with dlopen, call any of its exported functions through call by giving a type signature string, and release the handle with dlclose. scan finds the libraries in a list of directories, which is useful for finding the right file before you open it.

This is how Perl code here reaches a C library: pperl loads no compiled extension modules, so anything living in a .so is called either through this module or through one of the bindings built on it. pperl’s FFI::Platypus is one of them: its calls, types, records and closures are this module’s, so whatever FFI::Platypus can express, Peta::FFI can, in its own signature language.

Reach for it when:

  • The function you need lives in a system library (libm, libc, libuuid, libcrypto).

  • You already know the C prototype and want a one-liner, not a build step.

  • You are calling into a third-party .so that no pre-baked wrapper covers.

C calling back into Perl is supported: closure wraps a coderef as a function pointer you can hand to any C API that wants one (see the Callbacks category). C structs are records: record declares one as a Perl class, and a signature passes and returns it by value or by pointer (see the Records category). A library’s global variables are reached with symbol, fetch/store and variable.

What it does not do is derive types from a header: you read the C prototype and write the matching signature by hand, and for a struct its members in order, since a signature that disagrees with the real prototype is undefined behaviour rather than an error.

The two sibling modules Peta::FFI::Libc and Peta::FFI::UUID are pre-baked convenience wrappers built on top of this layer - they handle the dlopen / call / dlclose dance for you for a fixed set of functions. If your need is covered by one of them, prefer it over hand-rolled FFI.

A call that can block for good - statfs on a dead NFS mount - runs on a Peta::FFI::Pool thread instead, with a deadline, and reports how it went as an event. A library that calls back on threads of its own gets a queued closure, whose calls wait in a Peta::FFI::Queue for Perl’s event loop.

Signature strings#

Every call, function and closure takes a signature string of the form (args)ret, where each position is one type:

Type

C type

Perl side

v

void

return only; 1 is returned

i

int

integer

l

long

integer

L

unsigned long / size_t

unsigned integer

i8 i16 i32 i64

int8_t … int64_t

integer

u8 u16 u32 u64

uint8_t … uint64_t

unsigned integer

b

bool

perl’s true or false

d

double

float

f

float

float

D

long double

float (rounded to a double)

cf cd

float / double complex

[re, im]; a number is [n, 0]

p

const char *

input string; a returned string is copied

s

char * the caller frees

a result, copied, then freed; *s an out argument

P

mutable buffer

output buffer; \$buf is written

o

opaque pointer

raw address as integer

<Class>

a C object’s pointer

an object of a declared handle type

{Class}

struct by value

an object of a declared record class

*{Class}

struct *

the same object, by pointer

*T

T *

\$scalar in and out; undef is NULL

*<Class>

T ** C stores a handle in

\$scalar that gets the object

T[N] T[]

T * to an array

\@array in and out

T[0]

T *[] ended by NULL

\@array of o, p or handles

*T[r] *T[#k] *T[0]

T ** to an array C allocates

\$scalar or \@array it fills

*P[r] *P[#k]

char ** to bytes C allocates

\$scalar that gets the bytes

&T[r] &T[#k] &T[0]

const T ** to an array C keeps

the same, copied, never freed

&P[r] &P[#k]

const char ** to bytes C keeps

\$scalar that gets a copy

result &T[#k] &T[0]

const T * to an array C keeps

an array reference of copies

result &P[#k]

const char * to bytes C keeps

a copy of the bytes

A signature with no arguments is written ()ret, e.g. ()i for an int-returning no-arg function. Types may be separated by blanks: (u64 <My::Pool>)b. A variadic function lists its named parameters, a ;, and the types this call passes through its ...: (iL;o)i is ioctl(int, unsigned long, ...) passing one pointer.

An unsigned type reads its argument as an unsigned integer, so 2**64-1 passes as itself, not as -1, and returns above 2**63 come back unsigned. s is for C functions whose returned string the caller owns; function names the free function that releases it. <Class> is a typed handle declared with handle_type, {Class} a record declared with record. An argument is read once, as an XS function reads it, so a regex capture or a tied scalar passes its value.

*T and T[N] take the numbers, o, p (an array of C strings, char **) and b: (d*i)d is frexp(double, int *), called with \my $exp, and (i32[]LLo)v is qsort sorting \@numbers in place. T[N] also takes handles and records, and C’s changes to the records are written back: ({My::Point}[]LLo)v sorts \@points. T[0] is an array of o, p or handles that C reads up to a NULL the call appends, as execv’s argv or a NULL-terminated list of handles.

Some C functions hand over what they allocate through a pointer the caller passes. *<Class> is a handle stored there, as sqlite3_open stores its sqlite3 *: called with \my $db, $db becomes the object, owned by the caller and destroyed with it; a handle already in $db goes in, and stays the same object if C leaves it. *s is a string stored there, copied into the scalar and freed.

*T[r], *T[#k] and *T[0] are an array of T the callee allocates and stores there: as long as the function’s integer result ([r]), as argument k ([#k], an integer or an * integer, counted from 1), or up to a NULL element ([0], for o, p, s and handles). (<My::Conn>*<My::Item>[r]u32)i lists a connection’s items: called with \my $items, $items becomes an array reference of objects, each the caller’s; a reference to an array is filled instead. Strings (*s[...]) are copied and each freed, numbers and o read, records copied. A negative length is the callee’s error and leaves undef. The array itself is freed with function’s free - or, for records, with its release, (array, count), the library’s function that also frees what the records point to. Records keep the array until the last of them is gone, so a record’s p member reads its string for as long as the record lives; a bare copy of its bytes does not keep it.

*P[r] and *P[#k] are bytes the callee allocates and stores there, with the length as for an array: one byte string, NULs included, where *u8[#k] would give an array of numbers. librados’s rados_mon_command hands over its reply this way, as a char ** and a size_t * freed with rados_buffer_free: (op[]LPL*P[#7]*L*P[#9]*L)i with free => "rados_buffer_free", the input buffer passed as \$inbuf and the reply taken with \my $out, \my $outlen, \my $status, \my $statuslen. A NULL leaves undef, and so does a negative length; the buffer is freed with function’s free. A buffer ended by a NUL is an *s. Bytes the callee only lends are &P[r] and &P[#k], and &P[#k] is also a result: copied, never freed, as the lent arrays below.

&T[r], &T[#k] and &T[0] are the same for an array the callee only lends - a pointer into memory it keeps, as libxkbcommon’s xkb_keymap_key_get_syms_by_level hands out a keymap’s keysyms: (<Keymap>u32u32u32&u32[r])i. The elements are copied and the array is never freed. They are numbers, o, p, b or records; an s, which the caller frees, and a handle, whose object owns its pointer, cannot be lent.

&T[#k] and &T[0] are also a result: the array C returns, as libxkbcommon’s xkb_compose_table_entry_sequence returns an entry’s keysyms and stores their count through a size_t *: (o*L)&u32[#2], called as $f->($entry, \my $n). The call returns an array reference of the copies, or undef for a negative length or a NULL with elements. The length is read after the call, from an integer or an * integer argument; [r] is no form here, since the result is the array itself.

P passes the bytes of the scalar it references, which hold what the callee wrote afterwards: my $buf = "\0" x 64; call(..., \$buf). Given a string or a number instead, it passes a zeroed buffer of that length or that many bytes (at least 16) that nobody reads back.

o is the handle type for C APIs built on opaque structs (PGconn*, sqlite3*, MYSQL*): the raw address travels as a Perl integer, is never dereferenced by the runtime, and undef/0 passes NULL. Never use p for such pointers - p returns copy-until-NUL string semantics, which is wrong and unsafe for non-string data.

Memory primitives#

For C APIs that traffic in caller-owned memory (out-parameters, pointer arrays, length-counted buffers) the module provides the pointer toolbox: alloc/free, peek/peek_cstr, poke, and pack_ptr/unpack_ptr for bytes, fetch/store for a value of any type at an address, and sizeof/alignof. Addresses are ordinary Perl integers as with o. A pointer array (e.g. char *argv[]) is built portably as join "", map pack_ptr($_), @addrs poked into an alloced block, or passed as a p[] array.

Synopsis#

use Peta::FFI;

my $libm = Peta::FFI::dlopen("libm.so.6");
my $root = Peta::FFI::call($libm, "sqrt", "(d)d", 2.0);
Peta::FFI::dlclose($libm);
print $root, "\n";       # 1.4142135623731

Peta::FFI::record("Div", quot => "i", rem => "i");      # div_t
my $d = Peta::FFI::call(undef, "div", "(ii){Div}", 17, 5);
print $d->quot, " r ", $d->rem, "\n";                  # 3 r 2

Modules#

  • Peta::FFI::EventSource - events from a C library’s own threads, drained on Perl’s

  • Peta::FFI::LMDB - Lightning memory-mapped databases: transactional key-value stores in a file, over the system’s liblmdb.

  • Peta::FFI::Libc - Call common libc functions from Perl without writing a single line of FFI glue.

  • Peta::FFI::PCRE2 - Compile and run PCRE2 regular expressions via the system’s libpcre2-8 library.

  • Peta::FFI::Pool - Run C functions on worker threads, so that a call which hangs cannot hang the program.

  • Peta::FFI::Queue - Callbacks a C library makes from threads of its own, queued for Perl’s event loop.

  • Peta::FFI::UUID - Generate and parse RFC 4122 UUIDs via the system’s libuuid library.

Functions#

Library loading#

dlopen#

Open a shared library and return a handle suitable for passing to call.

dlclose#

Release a library handle previously returned by dlopen.

Function calls#

call#

Invoke a C function from an opened library and return its result.

function#

Prepare a C function once and return it as a code reference.

attach#

Prepare a C function once and install it as a named Perl sub.

errno#

errno as the last C call through Peta::FFI left it.

Symbol introspection#

symbol#

The address of a symbol - a function or a variable - in a library.

variable#

A library’s global variable as a Perl scalar.

scan#

Find the shared libraries in a list of directories.

Handles#

handle_type#

Declare a Perl class for a C object type, with its destructor.

release#

Destroy a handle’s C object now, ahead of its last reference.

Records#

record#

Declare a record class: a C struct, passed by value or by pointer.

union#

Declare a union class: a C union, reached by pointer and in memory.

sizeof#

The size of a type in bytes, as C’s sizeof.

alignof#

The alignment of a type in bytes, as C’s _Alignof.

Callbacks#

closure#

Wrap a Perl coderef as a C-callable function pointer.

closure_free#

Release a closure created by closure.

Memory#

fetch#

Read the value of a type at an address.

store#

Write a value of a type at an address.

peek#

Read $len raw bytes at address $ptr into a Perl byte string.

peek_cstr#

Read the NUL-terminated C string at address $ptr.

poke#

Copy a Perl byte string into memory at address $ptr.

alloc#

Allocate $len zeroed bytes of C memory; returns the address.

free#

Release memory obtained from alloc.

pack_ptr#

Pack an address into a native pointer-sized byte string.

unpack_ptr#

Unpack a native pointer-sized byte string into an address.