Node.js 26.1.0: нативный код без C++ — знакомимся с экспериментальным node:ffi

Node.js 26.1.0 (май 2026) получил встроенный экспериментальный модуль node:ffi, который позволяет вызывать функции из динамических библиотек (DLL, SO, dylib) прямо из JavaScript — без написания обёрток на C++ и без компиляции нативных аддонов. Это открывает доступ к тысячам существующих нативных библиотек, но требует осторожности: неправильный указатель или неверная сигнатура мгновенно «уронят» процесс.

Node.js 26.1.0: нативный код без C++
Node.js 26.1.0: нативный код без C++

Зачем FFI обычному разработчику

Представьте, что вам нужно воспользоваться мощной C-библиотекой — например, системной математикой libm, графикой raylib или даже SQLite. Раньше в Node.js был только один «официальный» путь: написать нативный аддон на C++ с использованием N-API или node-addon-api, скомпилировать его под каждую платформу и поддерживать совместимость с разными версиями Node.js. Это требовало знаний C++, инструментов сборки (node-gyp, CMake) и немало терпения.

FFI (Foreign Function Interface, «интерфейс внешних функций») меняет правила игры. Это механизм, позволяющий языку высокого уровня (в нашем случае JavaScript) напрямую вызывать функции из скомпилированных библиотек, зная лишь их сигнатуры — имена, типы аргументов и возвращаемые значения. Никакого промежуточного C++-кода, никакой компиляции при установке пакета.

До Node.js 26.1.0 разработчики использовали сторонние пакеты вроде ffi-napi или koffi, но это были пользовательские решения с собственными ограничениями. Теперь же в ядре Node.js появился родной node:ffi — официальный, встроенный и интегрированный с Permission Model.

Что привёз релиз 26.1.0

7 мая 2026 года вышла версия Node.js 26.1.0 (Current), и её главная изюминка — модуль node:ffi. Ключевые факты:

  • Флаг: API скрыто за флагом --experimental-ffi. Без него модуль недоступен.
  • Безопасность: если включена Permission Model, нужен дополнительный флаг --allow-ffi.
  • Статус: экспериментальный (Stability 1). API может измениться в любой следующей версии, использовать в продакшене не рекомендуется.
  • Автор: Paolo Insogna.

Модуль построен поверх libffi и использует современные возможности V8 для быстрых вызовов нативного кода [^10^].

Как это работает: три шага до нативной функции

Концепция проста, как ключ в замке:

  1. Открыть библиотеку — указать путь к .so, .dylib или .dll.
  2. Описать сигнатуру — сообщить Node.js, какие аргументы принимает функция и что возвращает.
  3. Вызвать — получить JavaScript-обёртку и работать с ней как с обычной функцией.

Минимальный рабочий пример

// Запускать с: node --experimental-ffi app.mjs

import { dlopen } from 'node:ffi';

// macOS: системная математическая библиотека
const path = '/usr/lib/libSystem.B.dylib';

const { functions } = dlopen(path, {
  sqrt: { parameters: ['f64'], result: 'f64' },
});

console.log(functions.sqrt(2)); // 1.4142135623730951

Здесь мы загрузили системную библиотеку libSystem.B.dylib, объявили, что функция sqrt принимает одно число с плавающей точкой (f64) и возвращает такое же, и сразу вызвали её. Никакого C++.

Кроссплатформенный путь к библиотеке

Чтобы не хардкодить расширения файлов, модуль предоставляет константу ffi.suffix:

import { suffix } from 'node:ffi';

// 'dylib' на macOS, 'so' на Linux, 'dll' на Windows
const libPath = `./libmyawesome.${suffix}`;

Типы данных: словарь для разговора с C

Поскольку JavaScript и C «говорят на разных языках», node:ffi использует текстовые обозначения типов. Вот основные из них:

ОбозначениеЧто означаетПример значения в JS
i8, int88-битное знаковое целоеnumber
u8, uint8, bool, char8-битное беззнаковое / логическое / символnumber (для bool передавайте 0 или 1)
i16, int1616-битное знаковое целоеnumber
u16, uint1616-битное беззнаковое целоеnumber
i32, int3232-битное знаковое целоеnumber
u32, uint3232-битное беззнаковое целоеnumber
i64, int6464-битное знаковое целоеbigint
u64, uint6464-битное беззнаковое целоеbigint
f32, float32-битное floatnumber
f64, double64-битное doublenumber
pointer, ptrУказательbigint или null
string, strC-строка (NUL-terminated UTF-8)string
bufferУказатель на BufferBuffer
arraybufferУказатель на ArrayBufferArrayBuffer
functionУказатель на функцию (callback)bigint

Важный нюанс: для 64-битных целых (i64/u64) нужно передавать bigint, а не обычные числа. Для логического типа bool принимаются числа 0 и 1; JavaScript-ные true/false напрямую не подходят.

Управление памятью и указателями

FFI — это мощь, но и ответственность. node:ffi не следит за временем жизни объектов и не проверяет корректность указателей. Официальная документация предупреждает: «Некорректные указатели, неправильные сигнатуры или обращение к памяти после ее освобождения могут привести к сбою процесса или повреждению памяти».

Чтение и запись по адресу

Модуль предоставляет «сырые» хелперы для работы с памятью:

import {
  getInt32,
  setInt32,
  toString,
  toBuffer,
} from 'node:ffi';

// Записать 32-битное целое по адресу ptr (bigint) со смещением 0
setInt32(ptr, 0, 42);

// Прочитать обратно
console.log(getInt32(ptr, 0)); // 42

// Прочитать C-строку по указателю
const str = toString(ptr);

// Обёрнуть кусок нативной памяти в Buffer (zero-copy)
const buf = toBuffer(ptr, 1024, false);

При использовании toBuffer и toArrayBuffer с copy: false вы получаете zero-copy представление чужой памяти. Если нативная сторона освободит эту память, а JavaScript продолжит с ней работать — последствия непредсказуемы.

Callbacks: JavaScript-функции для C

Иногда нативная библиотека ожидает указатель на функцию-обработчик. node:ffi умеет создавать такие обратные вызовы:

const { DynamicLibrary } = require('node:ffi');
const lib = new DynamicLibrary('./mylib.so');

const callbackPtr = lib.registerCallback(
  { parameters: ['i32'], result: 'i32' },
  (value) => value * 2,
);

// callbackPtr — bigint, который можно передать в нативную функцию

Ограничения строгие: callback должен выполняться в том же системном потоке, не должен бросать исключения, возвращать промисы или вызывать library.close() изнутри себя.

Поддерживаемые платформы

На момент релиза 26.1.0 встроенная libffi поддерживает:

  • macOS: arm64, x64
  • Windows: arm64, x64
  • Linux: arm, arm64, x64
  • FreeBSD: arm, arm64, x64

Другие платформы требуют сборки Node.js с внешней libffi (флаг --shared-ffi). Неофициальная GN-сборка node:ffi не поддерживает.

FFI vs N-API: когда что выбирать

Критерийnode:ffiN-API / нативные аддоны
Нужно писать C/C++НетДа
Компиляция при установкеНетДа (node-gyp/CMake)
СкоростьВысокая (прямые вызовы через V8 Fast API)Максимальная (нет маршаллинга)
БезопасностьНизкая (легко сломать память)Выше (типизация на уровне C++)
Сложные структурыОграниченноПолный контроль
Стабильность APIЭкспериментальнаяСтабильная

Правило большого пальца: если вам нужно быстро вызвать пару функций из готовой .so/.dllnode:ffi идеален. Если вы разрабатываете сложную нативную интеграцию с кастомными структурами данных и жёсткими требованиями к безопасности — N-API по-прежнему надёжнее.

Безопасность и Permission Model

Поскольку FFI открывает прямой доступ к системным вызовам и памяти, он считается привилегированной операцией. Если вы используете Permission Model (флаг --experimental-permission), вызов node:ffi потребует явного разрешения --allow-ffi. Это защищает от сценариев, когда скомпрометированный пакет из npm пытается загрузить произвольный системный код.

Также модуль реализует протокол явного управления ресурсами (Symbol.dispose), что позволяет использовать using для автоматического закрытия библиотек:

{
  using handle = dlopen('./mylib.so', { ... });
  // библиотека автоматически закроется при выходе из блока
}

Что дальше

node:ffi — это эксперимент, но за ним стоит серьёзная инженерная работа и долгая история обсуждений в сообществе Node.js . Если модуль стабилизируется, он кардинально упростит интеграцию с нативным миром: от системных утилит до игровых движков и научных библиотек.

Пока же — пробуйте, изучайте документацию и помните: с большой силой FFI приходит большая ответственность. Начните с простых вызовов, тщательно проверяйте сигнатуры и никогда не предполагайте, что «указатель ещё валиден», если нативная библиотека могла его освободить.

При использовании материалов сайта необходимо указывать ссылку на TGLand.ru. Если вы копируете фрагменты текста в интернете, прямая гиперссылка, доступная для индексации поисковыми системами, должна быть размещена в начале материала.

Вам также может понравиться