Skip to content

v0.4.3

Choose a tag to compare

@m3m0r7 m3m0r7 released this 20 Jun 02:09

Highlights

Callbacks: pass a PHP callable where a C function pointer is expected.

Until now every function-pointer parameter was generated as an opaque void *, so callback-taking C APIs (comparators, write/visit callbacks, and similar) couldn't actually be driven from PHP — PHP FFI rejects a closure for a void *. They are now generated as real C callback types, so a PHP closure or named callable is passed straight through and PHP FFI builds the C callback trampoline.

What you can do now

// int example_apply(int value, int (*callback)(int));
$result = Example::example_apply(20, static fn (int $v): int => $v * 2); // 41
  • Generated wrappers type a function-pointer parameter as callable (the cdata variant also accepts \FFI\CData).
  • The callback's arguments and return value are marshalled from the C signature in the cdef — an int arrives as a PHP int, a pointer arrives as an \FFI\CData.
  • Both a closure and a plain callable (a function name) are accepted.

Faithful, typed callback signatures

Each component of the callback keeps its real, declared type, so your closure receives something useful:

  • A pointer to a type the package already emits (its own struct/typedef) stays that type — e.g. int (*cb)(const struct point *) — so the closure gets a typed \FFI\CData and can read fields ($p->x) without a cast.
  • A const char * callback argument stays a char *, readable with \FFI::string().
  • Scalars keep their exact C width.
  • A component degrades to void * only when its spelling can't be placed in a function-pointer declarator (a nested function pointer, an array, or an anonymous aggregate); the whole parameter falls back to void * for a pointer-to-function-pointer or a by-value aggregate. A type referenced only through a callback signature is backfilled, so the cdef still loads.

Scope & caveats (PHP FFI itself)

  • Synchronous callbacks — invoked while the C call is on the stack (the common case) — are fully supported.
  • A stored / asynchronous callback (kept by C and called after the call returns) inherits PHP FFI's own trampoline-lifetime limitation and is not guaranteed safe.
  • Throwing out of a callback is fatal (Throwing from FFI callbacks is not allowed) — handle errors inside the callback and return a value.

Validation

New unit coverage for callback rendering and the backfill extractor, plus an end-to-end test that invokes both a closure and a named callable through the compiled native fixture. Generated golden snapshots updated. Rust (154) and PHP (62) suites green; clippy, php-cs-fixer, and phpstan clean.