FFI en PHP 7.4+: llamar a funciones C y usar bibliotecas nativas desde PHP

La FFI (Foreign Function Interface) de PHP, disponible desde PHP 7.4, permite llamar a funciones C y usar estructuras de datos de librerías nativas compiladas (.so en Linux, .dll en Windows) directamente desde PHP, sin necesidad de escribir una extensión en C.

Activar FFI

FFI debe estar habilitada en php.ini:

extension=ffi
ffi.enable=true  ; o "preload" en producción

FFI::cdef(): declarar funciones de libc

<?php
$ffi = FFI::cdef(
    "int printf(const char *format, ...);
     int abs(int j);
     double sqrt(double x);",
    null  // null = libc (cargada por defecto en el proceso)
);

echo $ffi->abs(-42) . "n";   // 42
echo $ffi->sqrt(144.0) . "n"; // 12
?>

Cargar una librería compartida

<?php
// Cargar libz (zlib) para compresión
$zlib = FFI::cdef(
    "unsigned long compressBound(unsigned long sourceLen);
     int compress2(
         unsigned char *dest,
         unsigned long *destLen,
         const unsigned char *source,
         unsigned long sourceLen,
         int level
     );
     int uncompress(
         unsigned char *dest,
         unsigned long *destLen,
         const unsigned char *source,
         unsigned long sourceLen
     );",
    "libz.so.1"
);

$fuente = "texto de ejemplo a comprimir";
$longFuente = strlen($fuente);
$longMax    = $zlib->compressBound($longFuente);

// Crear buffers C
$destino  = FFI::new("unsigned char[$longMax]");
$longDest = FFI::new("unsigned long[1]");
$longDest[0] = $longMax;

$ret = $zlib->compress2($destino, $longDest, $fuente, $longFuente, 6);
echo "Comprimido: {$longDest[0]} bytes (ret=$ret)n";
?>

FFI::new(): crear tipos de datos C

<?php
// Entero en memoria C
$entero = FFI::new("int");
$entero->cdata = 42;
echo $entero->cdata . "n"; // 42

// Array de enteros
$array = FFI::new("int[10]");
for ($i = 0; $i < 10; $i++) {
    $array[$i] = $i * $i;
}
echo $array[5] . "n"; // 25

// Struct
$ffi = FFI::cdef("
    typedef struct {
        float x;
        float y;
        float z;
    } Vec3;
");

$vec = $ffi->new("Vec3");
$vec->x = 1.0;
$vec->y = 2.5;
$vec->z = -0.5;
echo "{$vec->x}, {$vec->y}, {$vec->z}n";
?>

Usar libsodium para cifrado

<?php
// libsodium ya está disponible en PHP como extensión nativa
// pero como ejemplo de FFI con una librería de seguridad:

$sodium = FFI::load('/etc/php/ffi/libsodium.h');  // precompilado

$mensaje = "Mensaje secreto";
$clave   = FFI::new("unsigned char[32]");
// Generar clave aleatoria
$sodium->randombytes_buf($clave, 32);
?>

FFI::load(): cargar desde cabecera preprocesada

Para librerías grandes, es más cómodo preparar un fichero de cabecera con solo las declaraciones que necesitas:

/* mi_lib.h */
#define FFI_SCOPE "MI_LIB"
#define FFI_LIB   "libmicosa.so.1"

typedef struct {
    int id;
    char nombre[64];
} Registro;

int obtener_registro(int id, Registro *out);
void liberar_registro(Registro *r);
<?php
$ffi = FFI::load('mi_lib.h');

$reg = $ffi->new("Registro");
$ret = $ffi->obtener_registro(42, FFI::addr($reg));

if ($ret === 0) {
    echo FFI::string($reg->nombre) . "n";
}
?>

Preloading para producción

En producción, con ffi.enable=preload, las declaraciones FFI deben cargarse en el script de preload:

<?php
// preload.php
FFI::load('/var/www/app/ffi/mi_lib.h');
?>
opcache.preload=/var/www/app/preload.php
ffi.enable=preload

Cuándo usar FFI

  • Cuando necesitas una librería C sin extensión PHP disponible (procesamiento de señales de audio, visión por computador, codecs especiales).
  • Para prototipar el binding de una librería antes de escribir una extensión en C.
  • Operaciones de bajo nivel sobre memoria que PHP no expone directamente.

Errores comunes

  • FFI not enabled: verifica que extension=ffi está en php.ini y que ffi.enable no es false.
  • Librería no encontrada: el nombre de la librería debe ser exacto (libz.so.1, no libz). Usa ldconfig -p | grep libz para ver el nombre exacto.
  • Segfault por puntero incorrecto: un puntero NULL o un buffer demasiado pequeño bloquea el proceso entero. FFI no tiene las protecciones de PHP; los errores son fatales a nivel de proceso.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP