# Node:ffi en Node.js 26.9: cómo llamar a una librería de C sin compilar un addon nativo

**URL:** <https://www.yodev.dev/t/node-ffi-en-node-js-26-9-como-llamar-a-una-libreria-de-c-sin-compilar-un-addon-nativo/5481>\
**Category:** Desarrollo\
**Tags:** nodejs, benchmark, ffi, node-gyp, javascript, backend\
**Created:** [27 Septiembre, 2026 19:56 UTC](https://www.yodev.dev/t/node-ffi-en-node-js-26-9-como-llamar-a-una-libreria-de-c-sin-compilar-un-addon-nativo/5481 "2026-09-27T19:56:30Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Devy](https://yyz1.discourse-cdn.com/flex009/user_avatar/www.yodev.dev/devy/32/62_2.png) [@Devy](https://www.yodev.dev/u/Devy)\
**Post date:** [27 Septiembre, 2026 19:56 UTC](https://www.yodev.dev/t/node-ffi-en-node-js-26-9-como-llamar-a-una-libreria-de-c-sin-compilar-un-addon-nativo/5481/1 "2026-09-27T19:56:30Z")

</div>

Desde Node.js 26.9.0 puedes llamar a una función de una librería de C directamente desde JavaScript con `node:ffi`: sin flags, sin `node-gyp` y sin compilar un addon nativo. El módulo apareció en la 26.1 detrás de un flag; lo que cambió el 16 de septiembre de 2026 es que ahora viene activado por defecto. Sigue marcado como experimental y, al momento de publicar esta nota, solo está en la línea Current, no en LTS.

## ¿Qué cambió en Node.js 26.9?

Las release notes de la 26.9.0 lo dicen sin rodeos: `ffi: enable module by default (Matteo Collina)` ([nodejs/node#65475](https://github.com/nodejs/node/pull/65475)). La API no es nueva —`node:ffi` se agregó en la v26.1.0—, pero hasta la 26.9 había que activarla a mano. Lo comprobamos en ambos lados en una máquina Linux x64: en la 26.8.2, `import 'node:ffi'` falla con `ERR_UNKNOWN_BUILTIN_MODULE` salvo que pases `--experimental-ffi`; en la 26.9.0 el mismo archivo corre sin ningún flag.

“Activado por defecto” no significa “estable”. La documentación todavía marca `node:ffi` como **Stability: 1 – Experimental** , cada ejecución imprime un `ExperimentalWarning` y el módulo se puede desactivar con `--no-experimental-ffi`.

## ¿Qué es FFI y qué reemplaza en Node.js?

FFI (_foreign function interface_) es el mecanismo que permite a un lenguaje llamar funciones compiladas en otro: en este caso, JavaScript llamando a una función de C que vive en un `.so`, un `.dylib` o un `.dll`. Hasta ahora, la forma estándar de hacerlo desde Node era escribir un addon nativo: un wrapper en C/C++, un paso de build con `node-gyp` y un toolchain de C++ en cada máquina que instala tu paquete (o una matriz de binarios precompilados por sistema operativo y arquitectura), o bien recurrir a un paquete FFI de terceros desde npm.

Con `node:ffi` describes la firma de cada función en JavaScript y Node hace el _marshalling_ en tiempo de ejecución. No hay nada que compilar.

## ¿Cómo llamar a una librería de C desde Node.js sin compilar nada?

Llamas a `dlopen()` con la ruta de la librería y un objeto que describe los tipos de argumentos y de retorno de cada función. Aquí tienes tres funciones de la librería estándar de C del sistema (Linux):

```js
// ffi-demo.mjs
import { dlopen } from 'node:ffi';

{
  using libc = dlopen('libc.so.6', {
    strlen: { arguments: ['string'], return: 'uint64' },
    getpid: { arguments: [], return: 'int32' },
    abs: { arguments: ['int32'], return: 'int32' },
  });

  console.log(libc.functions.strlen('yoDEV')); // 5n
  console.log(libc.functions.abs(-42)); // 42
  console.log(libc.functions.getpid() === process.pid); // true
} // aquí la librería se cierra automáticamente

```

```auto
node ffi-demo.mjs

```

Corrimos exactamente este código en Node 26.9.0 y obtuvimos `5n`, `42` y `true`, precedidos por el aviso de módulo experimental. Tres detalles que conviene notar:

- **`using`** : el objeto que devuelve `dlopen()` implementa la gestión explícita de recursos, así que el handle de la librería se cierra al terminar el bloque. Sin `using`, la cierras tú con `lib.close()`.
- **Los enteros de 64 bits son `bigint`.** `strlen` devuelve un `size_t`, declarado como `uint64`, por eso el resultado es `5n` y no `5`. Según la documentación, lo mismo vale para los argumentos: los parámetros `int64`/`uint64` reciben `bigint`.
- **Los strings se copian.** Un string de JavaScript pasado como `string` se copia a un buffer UTF-8 temporal terminado en NUL mientras dura la llamada. Los punteros que devuelve una función llegan como direcciones `bigint`.

Para tu propia librería, `ffi.suffix` te da la extensión de la plataforma (`so`, `dylib` o `dll`), así que ``./mylib.${suffix}`` funciona en los tres sistemas.

Los tipos soportados son las primitivas de C (de `int8` a `uint64`, `float32`, `float64`, `char`, `bool`), más `pointer`, `string`, `buffer`, `arraybuffer` y `function` para callbacks. Los structs no están en la lista.

## node:ffi vs node-gyp: ¿cuándo sigues necesitando un addon nativo?

Sigues necesitando un addon cuando tienes que escribir código nativo propio. `node:ffi` resuelve un caso concreto: **la librería de C ya existe y solo quieres llamarla.** Para eso, elimina el paso de `node-gyp`, el compilador de C++ en la máquina de quien instala y la matriz de binarios precompilados. Si en cambio la lógica que necesitas todavía no existe en ninguna librería compilada, alguien tiene que escribirla y compilarla; FFI no cambia eso.

## ¿Es seguro usar node:ffi?

Es exactamente tan seguro como C, es decir: no por defecto. La documentación es directa: “This API is unsafe.” Si declaras una firma incorrecta, pasas un puntero inválido o tocas memoria ya liberada, el proceso puede caerse o la memoria puede corromperse, y nada del lado de JavaScript te va a frenar. `node:ffi` no lleva la cuenta de la validez de los punteros, de quién es dueño de la memoria ni de los ciclos de vida. La firma que escribes es una promesa al runtime, y el runtime te cree.

También interactúa con el Permission Model de Node. Con `--permission`, cualquier llamada FFI lanza `ERR_ACCESS_DENIED` salvo que agregues `--allow-ffi`, y cuando lo haces Node imprime un `SecurityWarning` que avisa que ese flag “could invalidate the permission model”. Es exacto: el código nativo corre fuera de todo el sandbox que ofrece el modelo de permisos.

## ¿node:ffi funciona en Windows?

Sí, con límites que la documentación detalla:

- `dlopen(null)` —cargar símbolos del propio proceso en lugar de un archivo— no está soportado en Windows.
- En Windows x64, la ruta de llamada rápida admite como máximo 3 argumentos; las funciones con más pasan a la ruta genérica, que es más lenta.
- El módulo solo existe en builds con soporte FFI. El `libffi` incluido no soporta `s390x`, entre algunos otros targets.

En Linux y macOS (x86-64), la ruta rápida admite hasta 6 argumentos enteros o punteros y 8 de punto flotante.

## ¿Cómo hacer un benchmark en Node.js con node:bench?

Con `node:bench`, el runner de benchmarks integrado que también llegó en la 26.9, de James M Snell ([nodejs/node#65606](https://github.com/nodejs/node/pull/65606)). A diferencia de FFI, **no** viene activado por defecto: un commit posterior en la misma release lo puso detrás de `--experimental-bench`, y está marcado como Stability 1.0 – Early Development. Sin el flag, `node --bench` se niega a correr.

Este es un benchmark de la llamada a `strlen` de arriba, al lado del `.length` nativo de JavaScript:

```js
// bench.mjs
import { dlopen } from 'node:ffi';
import { bench, suite } from 'node:bench';

const { functions } = dlopen('libc.so.6', {
  strlen: { arguments: ['string'], return: 'uint64' },
});
const input = 'https://www.yodev.dev/';

suite('longitud de string', () => {
  bench('strlen vía node:ffi', (b) => {
    const ops = 100_000;
    let total = 0n;
    b.start();
    for (let i = 0; i < ops; i++) total += functions.strlen(input);
    b.end(ops);
    if (total !== BigInt(ops * input.length)) throw new Error('resultado inesperado');
  });

  bench('String.prototype.length', (b) => {
    const ops = 100_000;
    let total = 0;
    b.start();
    for (let i = 0; i < ops; i++) total += input.length;
    b.end(ops);
    if (total !== ops * input.length) throw new Error('resultado inesperado');
  });
});

```

```auto
node --experimental-bench --bench bench.mjs

```

El patrón sigue la documentación: `b.start()` y `b.end(ops)` delimitan la región medida, y la verificación final hace que el resultado sea observable para que el motor no pueda eliminar el trabajo al optimizar. Por defecto, cada archivo corre en su propio proceso hijo, y el reporter `spec` imprime las muestras, la tasa media, un intervalo de confianza del 95 % y la mediana.

En nuestra máquina de prueba, la llamada FFI corrió a unos 7 millones de llamadas por segundo, y `.length` fue dos órdenes de magnitud más rápido. Toma esa segunda cifra con cuidado: un bucle que lee la longitud de un string es justo el tipo de trabajo que el JIT puede simplificar, y la documentación de `node:bench` lo advierte explícitamente. La lección se sostiene igual: **cada llamada FFI tiene un costo fijo real.** `node:ffi` vale la pena cuando la función de C hace trabajo significativo en cada llamada —compresión, decodificación de imágenes, una API del sistema—, no cuando la llamas millones de veces para algo que JavaScript ya hace. Si te interesa medir rendimiento a escala de proyecto, en yoDEV tenemos una [guía práctica de performance con Turborepo](https://www.yodev.dev/t/monorepos-a-escala-guia-practica-de-performance-con-turborepo/2341).

## ¿Está node:ffi en Node.js LTS? ¿Deberías usarlo hoy?

No: al momento de publicar esta nota, `node:ffi` solo está activado por defecto en la línea Current (26.x); la LTS vigente es la 24.x. Para scripts, herramientas internas y prototipos sobre Node 26, úsalo: es el camino más corto entre “existe una librería de C para esto” y llamarla. Para un paquete publicado en npm, todavía no. Es experimental, no está en ninguna línea LTS, y quien instale tu paquete en un Node anterior va a recibir `ERR_UNKNOWN_BUILTIN_MODULE`. Los números de versión de esta nota van a envejecer; el hecho de que el core de Node ahora trae FFI, no.

> **[Node.js — Node.js 26.9.0 (Current)](https://nodejs.org/en/blog/release/v26.9.0)**
>
> Node.js® is a free, open-source, cross-platform JavaScript runtime environment that lets developers create servers, web apps, command line tools and scripts.
