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=ffiestá enphp.iniy queffi.enableno esfalse. - Librería no encontrada: el nombre de la librería debe ser exacto (
libz.so.1, nolibz). Usaldconfig -p | grep libzpara 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.
