Spec-Zone.ru › Node.js

FFI

Исходный код: lib/ffi.jsДобавлено в: v26.1.0

Стабильность: 1 — Экспериментальный

Модуль node:ffi предоставляет экспериментальный интерфейс вызова внешних функций для загрузки динамических библиотек и вызова нативных символов из JavaScript.

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

Для доступа к нему:

Модули JavaScript
import ffi from 'node:ffi';
CommonJS
const ffi = require('node:ffi');

Этот модуль доступен только по схеме node: в сборках с поддержкой FFI и требует флага --experimental-ffi.

Сборка Node.js с поддержкой node:ffi доступна с помощью включённой в комплект библиотеки libffi на платформах, где libffi предоставляет совместимую статическую серверную часть, либо с помощью общей библиотеки libffi с флагом конфигурации --shared-ffi. Неофициальная сборка GN не поддерживает node:ffi.

Следующие целевые платформы не поддерживаются включённой в комплект libffi:

  • s390x.
  • mips, mipsel и mips64el на целевых платформах, отличных от FreeBSD, Linux и OpenBSD.
  • ppc64 на Android, CloudABI, iOS, OpenHarmony, OS/400, Solaris и Windows.

При использовании модели разрешений API FFI ограничены, если не указан флаг --allow-ffi.

Обзор

Модуль node:ffi предоставляет две группы API:

  • API динамических библиотек для загрузки библиотек, разрешения символов и создания вызываемых обёрток JavaScript.
  • Вспомогательные средства для работы с необработанной памятью: чтение и запись примитивных значений через указатели, преобразование указателей в строки JavaScript, экземпляры Buffer и экземпляры ArrayBuffer, а также копирование данных обратно в нативную память.

Имена типов

В сигнатурах FFI используются строковые имена типов.

Поддерживаемые имена типов:

  • void
  • i8, int8
  • u8, uint8, bool, char
  • i16, int16
  • u16, uint16
  • i32, int32
  • u32, uint32
  • i64, int64
  • u64, uint64
  • f32, float
  • f64, double
  • pointer, ptr
  • string, str
  • buffer
  • arraybuffer
  • function

Эти имена типов также доступны в виде констант в ffi.types:

  • ffi.types.VOID = 'void'
  • ffi.types.POINTER = 'pointer'
  • ffi.types.BUFFER = 'buffer'
  • ffi.types.ARRAY_BUFFER = 'arraybuffer'
  • ffi.types.FUNCTION = 'function'
  • ffi.types.BOOL = 'bool'
  • ffi.types.CHAR = 'char'
  • ffi.types.STRING = 'string'
  • ffi.types.FLOAT = 'float'
  • ffi.types.DOUBLE = 'double'
  • ffi.types.INT_8 = 'int8'
  • ffi.types.UINT_8 = 'uint8'
  • ffi.types.INT_16 = 'int16'
  • ffi.types.UINT_16 = 'uint16'
  • ffi.types.INT_32 = 'int32'
  • ffi.types.UINT_32 = 'uint32'
  • ffi.types.INT_64 = 'int64'
  • ffi.types.UINT_64 = 'uint64'
  • ffi.types.FLOAT_32 = 'float32'
  • ffi.types.FLOAT_64 = 'float64'

Типы, подобные указателям (pointer, string, buffer, arraybuffer и function), передаются через нативный слой как указатели.

Когда Buffer, ArrayBuffer или значения типизированных массивов передаются в качестве аргументов, подобных указателям, Node.js заимствует необработанный указатель на их память на время нативного вызова. Вызывающий код должен гарантировать, что эта память остаётся действительной и неизменной на протяжении всего вызова.

Изменять размер, переносить, отсоединять или иным образом делать эту память недействительной во время выполнения нативного вызова не поддерживается и опасно. Это касается и повторного входа в JavaScript, например через обратные вызовы FFI. Такие действия могут привести к аварийному завершению процесса, некорректному выводу или повреждению памяти.

Тип char соответствует ABI C платформы. На платформах, где обычный тип C char знаковый, он ведёт себя как i8; в противном случае — как u8.

Тип bool маршалируется как 8-битное беззнаковое целое. Передавайте числовые значения, например 0 и 1; значения JavaScript true и false не принимаются.

Объекты сигнатур

Функции и обратные вызовы описываются объектами сигнатур.

Объекты сигнатур могут содержать следующие свойства; оба свойства необязательны:

  • return <string> Имя типа, задающее тип возвращаемого значения функции или обратного вызова. Значение по умолчанию: 'void'.
  • arguments <string[]> Массив имён типов, задающий список типов аргументов функции или обратного вызова. Значение по умолчанию: [].
const signature = {
  return: 'i32',
  arguments: ['i32', 'i32'],
};
jscopy

ffi.suffix

Добавлено в: v26.1.0
  • <string>

Расширение нативной динамической библиотеки для текущей платформы:

  • 'dylib' в macOS
  • 'so' на Unix-подобных платформах
  • 'dll' в Windows

Это можно использовать для создания переносимых путей к библиотекам:

const { suffix } = require('node:ffi');

const path = `libsqlite3.${suffix}`;
cjscopy

ffi.dlopen(path[, definitions])

Добавлено в: v26.1.0
  • path <string> | <null> Путь к динамической библиотеке или null для разрешения символов из образа текущего процесса.
  • definitions <Object> Определения символов, которые нужно разрешить немедленно.
  • Возвращает: <Object>

Загружает динамическую библиотеку и разрешает запрошенные определения функций.

Передача null в Windows не поддерживается.

Если definitions не указан, functions возвращается как пустой объект до явного разрешения символов.

Возвращаемый объект содержит:

  • lib {DynamicLibrary} Дескриптор загруженной библиотеки.
  • functions <Object> Вызываемые обёртки для запрошенных символов.

Возвращаемый объект также реализует протокол явного управления ресурсами, поэтому его можно использовать с объявлением using. Освобождение возвращаемого объекта закрывает дескриптор библиотеки.

import { dlopen } from 'node:ffi';

{
  using handle = dlopen('./mylib.so', {
    add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
  });
  console.log(handle.functions.add_i32(20, 22));
} // handle.lib.close() is invoked automatically here.
mjscopy
Модули JavaScript
import { dlopen } from 'node:ffi';

const { lib, functions } = dlopen('./mylib.so', {
  add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
  string_length: { arguments: ['pointer'], return: 'u64' },
});

console.log(functions.add_i32(20, 22));
CommonJS
const { dlopen } = require('node:ffi');

const { lib, functions } = dlopen('./mylib.so', {
  add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
  string_length: { arguments: ['pointer'], return: 'u64' },
});

console.log(functions.add_i32(20, 22));

ffi.dlclose(handle)

Добавлено в: v26.1.0
  • handle {DynamicLibrary}

Закрывает динамическую библиотеку.

Это эквивалентно вызову handle.close().

ffi.dlsym(handle, symbol)

Добавлено в: v26.1.0
  • handle {DynamicLibrary}
  • symbol <string>
  • Возвращает: <bigint>

Разрешает адрес символа в загруженной библиотеке.

Это эквивалентно вызову handle.getSymbol(symbol).

Класс: DynamicLibrary

Добавлено в: v26.1.0

Представляет загруженную динамическую библиотеку.

new DynamicLibrary(path)

  • path <string> | <null> Путь к динамической библиотеке или null для разрешения символов из образа текущего процесса.

Загружает динамическую библиотеку, не разрешая функции заранее.

Передача null в Windows не поддерживается.

const { DynamicLibrary } = require('node:ffi');

const lib = new DynamicLibrary('./mylib.so');
cjscopy

library.path

  • <string>

Путь, использованный для загрузки библиотеки.

library.functions

  • <Object>

Объект с ранее разрешёнными обёртками функций.

library.symbols

  • <Object>

Объект с ранее разрешёнными адресами символов в виде значений bigint.

library.close()

Закрывает дескриптор библиотеки.

DynamicLibrary реализует протокол явного управления ресурсами, поэтому экземпляром библиотеки можно управлять с помощью объявления using. При выходе из охватывающей области видимости автоматически вызывается library.close().

import { DynamicLibrary } from 'node:ffi';

{
  using lib = new DynamicLibrary('./mylib.so');
  // Use `lib` here; `lib.close()` is called when the block exits.
}
mjscopy

Повторный вызов library.close() (или освобождение библиотеки) ничего не делает.

После закрытия библиотеки:

  • Разрешённые обёртки функций становятся недействительными.
  • Последующие попытки разрешить символы и функции приводят к исключению.
  • Зарегистрированные обратные вызовы становятся недействительными.

Закрытие библиотеки не делает ранее экспортированные указатели обратных вызовов безопасными для повторного использования. Node.js не отслеживает и не отзывает указатели обратных вызовов, уже переданные нативному коду.

Если нативный код всё ещё хранит указатель обратного вызова после library.close() или после library.unregisterCallback(pointer), его вызов имеет неопределённое поведение, запрещён и опасен: он может привести к аварийному завершению процесса, некорректному выводу или повреждению памяти. Нативный код должен прекратить использовать адреса обратных вызовов до закрытия библиотеки или отмены регистрации обратного вызова.

Вызов library.close() из активного обратного вызова библиотеки не поддерживается и опасен. Обратный вызов должен завершиться до закрытия библиотеки.

library[Symbol.dispose]()

Добавлено в: v26.1.0

Вызывает library.close(). Это позволяет использовать экземпляры DynamicLibrary с объявлением using для автоматической очистки при выходе из охватывающей области видимости. Для уже закрытой библиотеки вызов ничего не делает.

library.getFunction(name, signature)

  • name <string>
  • signature <Object>
  • Возвращает: <Function>

Разрешает символ и возвращает вызываемую обёртку JavaScript.

Возвращаемая функция имеет свойство .pointer, содержащее адрес нативной функции в виде bigint.

Если символ уже был разрешён, повторный запрос с другой сигнатурой приводит к исключению.

const { DynamicLibrary } = require('node:ffi');

const lib = new DynamicLibrary('./mylib.so');
const add = lib.getFunction('add_i32', {
  arguments: ['i32', 'i32'],
  return: 'i32',
});

console.log(add(20, 22));
console.log(add.pointer);
cjscopy

library.getFunctions([definitions])

  • definitions <Object>
  • Возвращает: <Object>

Если указан definitions, разрешает каждый именованный символ и возвращает объект с вызываемыми обёртками.

Если definitions не указан, возвращает обёртки для всех функций, которые уже были разрешены в библиотеке.

library.getSymbol(name)

  • name <string>
  • Возвращает: <bigint>

Разрешает символ и возвращает его нативный адрес в виде bigint.

library.getSymbols()

  • Возвращает: <Object>

Возвращает объект со всеми ранее разрешёнными адресами символов.

library.registerCallback([signature,] callback)

  • signature <Object>
  • callback <Function>
  • Возвращает: <bigint>

Создаёт указатель нативного обратного вызова, связанный с функцией JavaScript.

Если signature не указан, для обратного вызова используется сигнатура void () по умолчанию.

Возвращаемое значение — адрес указателя обратного вызова в виде bigint. Его можно передать нативным функциям, ожидающим указатель обратного вызова.

const { DynamicLibrary } = require('node:ffi');

const lib = new DynamicLibrary('./mylib.so');

const callback = lib.registerCallback(
  { arguments: ['i32'], return: 'i32' },
  (value) => value * 2,
);
cjscopy

На обратные вызовы распространяются следующие ограничения:

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

Закрывать библиотеку или отменять регистрацию выполняющегося в данный момент обратного вызова изнутри него не поддерживается и опасно. Это может привести к аварийному завершению процесса, некорректному выводу или повреждению памяти.

library.unregisterCallback(pointer)

  • pointer <bigint>

Освобождает обратный вызов, ранее созданный с помощью library.registerCallback().

Вызов library.unregisterCallback(pointer) для выполняющегося в данный момент обратного вызова не поддерживается и опасен. Обратный вызов должен завершиться до отмены его регистрации.

После возврата library.unregisterCallback(pointer) вызов этого указателя обратного вызова из нативного кода имеет неопределённое поведение, запрещён и опасен: он может привести к аварийному завершению процесса, некорректному выводу или повреждению памяти.

library.refCallback(pointer)

  • pointer <bigint>

Сохраняет сильную ссылку JavaScript на обратный вызов.

library.unrefCallback(pointer)

  • pointer <bigint>

Позволяет JavaScript хранить слабую ссылку на обратный вызов.

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

Вызов нативных функций

Преобразование аргументов зависит от объявленного типа FFI.

Для целочисленных типов шириной 8, 16 и 32 бита и типов с плавающей точкой передавайте значения JavaScript number, соответствующие объявленному типу.

Для целочисленных типов шириной 64 бита (i64 и u64) передавайте значения JavaScript bigint.

Для аргументов, подобных указателям:

  • null и undefined передаются как нулевые указатели.
  • string копируются во временные UTF-8-строки с завершающим NUL на время вызова.
  • Buffer, типизированные массивы и экземпляры DataView передают указатель на свою память.
  • ArrayBuffer передаёт указатель на свою память.
  • bigint передаются как необработанные адреса указателей.

Возвращаемые значения-указатели предоставляются в виде адресов bigint.

Вспомогательные средства для доступа к примитивным значениям в памяти

Следующие вспомогательные функции читают и записывают примитивные значения по нативному указателю, при необходимости с байтовым смещением:

  • ffi.getInt8(pointer[, offset])
  • ffi.getUint8(pointer[, offset])
  • ffi.getInt16(pointer[, offset])
  • ffi.getUint16(pointer[, offset])
  • ffi.getInt32(pointer[, offset])
  • ffi.getUint32(pointer[, offset])
  • ffi.getInt64(pointer[, offset])
  • ffi.getUint64(pointer[, offset])
  • ffi.getFloat32(pointer[, offset])
  • ffi.getFloat64(pointer[, offset])
  • ffi.setInt8(pointer, offset, value)
  • ffi.setUint8(pointer, offset, value)
  • ffi.setInt16(pointer, offset, value)
  • ffi.setUint16(pointer, offset, value)
  • ffi.setInt32(pointer, offset, value)
  • ffi.setUint32(pointer, offset, value)
  • ffi.setInt64(pointer, offset, value)
  • ffi.setUint64(pointer, offset, value)
  • ffi.setFloat32(pointer, offset, value)
  • ffi.setFloat64(pointer, offset, value)

Эти вспомогательные функции напрямую читают и записывают память. pointer должен быть bigint, указывающим на допустимую для чтения или записи нативную память. Если указан offset, он интерпретируется как байтовое смещение относительно pointer.

Функции чтения возвращают значения JavaScript number для целочисленных типов шириной 8, 16 и 32 бита и для типов с плавающей точкой. Для целочисленных типов шириной 64 бита они возвращают значения bigint.

Функции записи требуют явного байтового смещения и перед записью в память проверяют предоставленное значение JavaScript на соответствие целевому нативному типу. Для setInt64() и setUint64() значения bigint принимаются напрямую; числовые входные значения должны быть целыми числами в безопасном диапазоне целых чисел JavaScript.

const {
  getInt32,
  setInt32,
} = require('node:ffi');

setInt32(ptr, 0, 42);
console.log(getInt32(ptr, 0));
cjscopy

Как и другие средства работы с необработанной памятью в этом модуле, эти API не отслеживают владение, границы и время жизни. Передача недопустимого указателя, использование неверного смещения или запись через устаревший указатель может повредить память или привести к аварийному завершению процесса.

ffi.toString(pointer)

Добавлено в: v26.1.0
  • pointer <bigint>
  • Возвращает: <string> | <null>

Читает из нативной памяти строку UTF-8 с завершающим NUL.

Если pointer имеет значение 0n, возвращается null.

Эта функция не проверяет, указывает ли pointer на доступную для чтения память и завершаются ли данные по указанному адресу значением \0. Передача недопустимого указателя, указателя на освобождённую память или указателя на байты без завершающего NUL может привести к чтению посторонней памяти, аварийному завершению процесса, усечённому или искажённому выводу.

const { toString } = require('node:ffi');

const value = toString(ptr);
cjscopy

ffi.toBuffer(pointer, length[, copy])

Добавлено в: v26.1.0
  • pointer <bigint>
  • length <number>
  • copy <boolean> Если false, создаётся представление без копирования. Значение по умолчанию: true.
  • Возвращает: <Buffer>

Создаёт Buffer из нативной памяти.

Если copy имеет значение true, возвращаемый Buffer владеет собственной копией памяти. Если copy имеет значение false, возвращаемый Buffer напрямую ссылается на исходную нативную память.

Использование copy: false — это способ обойти копирование данных. Возвращаемый Buffer представляет собой доступное для записи представление внешней памяти, поэтому запись в JavaScript напрямую изменяет исходную нативную память. Вызывающий код должен гарантировать, что:

  • pointer остаётся действительным на протяжении всего времени жизни возвращённого Buffer.
  • length находится в пределах выделенной области нативной памяти.
  • Нативный код не освобождает и не использует эту память повторно, пока JavaScript продолжает использовать Buffer.
  • Соблюдаются правила защиты памяти. Например, в страницы памяти только для чтения нельзя записывать.

Если эти условия не соблюдены, чтение или запись Buffer может повредить память или привести к аварийному завершению процесса.

ffi.toArrayBuffer(pointer, length[, copy])

Добавлено в: v26.1.0
  • pointer <bigint>
  • length <number>
  • copy <boolean> Если false, создаётся представление без копирования. Значение по умолчанию: true.
  • Возвращает: <ArrayBuffer>

Создаёт ArrayBuffer из нативной памяти.

Если copy имеет значение true, возвращаемый ArrayBuffer содержит копию байтов. Если copy имеет значение false, возвращаемый ArrayBuffer напрямую ссылается на исходную нативную память.

Здесь действуют те же требования к времени жизни и границам, что и для ffi.toBuffer(pointer, length, copy). При использовании copy: false возвращаемый ArrayBuffer является представлением внешней памяти без копирования и безопасен только до тех пор, пока эта память выделена, её структура не изменена и вся предоставленная область остаётся действительной.

ffi.exportString(string, pointer, length[, encoding])

Добавлено в: v26.1.0
  • string <string>
  • pointer <bigint>
  • length <number>
  • encoding <string> Значение по умолчанию: 'utf8'.

Копирует строку JavaScript в нативную память и добавляет завершающий NUL.

length должен быть достаточно большим, чтобы вместить всю закодированную строку вместе с завершающим NUL. Для кодировок UTF-16 и UCS-2 завершающий символ представлен двумя нулевыми байтами.

pointer должен указывать на доступную для записи нативную память как минимум с length байтами свободного пространства. Эта функция не выделяет память самостоятельно.

string должен быть строкой JavaScript. encoding должен быть строкой.

ffi.exportBuffer(buffer, pointer, length)

Добавлено в: v26.1.0
  • buffer <Buffer>
  • pointer <bigint>
  • length <number>

Копирует байты из Buffer в нативную память.

length должен быть не меньше buffer.length.

pointer должен указывать на доступную для записи нативную память как минимум с length байтами свободного пространства. Эта функция не выделяет память самостоятельно.

buffer должен быть объектом Buffer Node.js.

ffi.exportArrayBuffer(arrayBuffer, pointer, length)

Добавлено в: v26.1.0
  • arrayBuffer <ArrayBuffer>
  • pointer <bigint>
  • length <number>

Копирует байты из ArrayBuffer в нативную память.

length должен быть не меньше arrayBuffer.byteLength.

pointer должен указывать на доступную для записи нативную память как минимум с length байтами свободного пространства. Эта функция не выделяет память самостоятельно.

ffi.exportArrayBufferView(arrayBufferView, pointer, length)

Добавлено в: v26.1.0
  • arrayBufferView {ArrayBufferView}
  • pointer <bigint>
  • length <number>

Копирует байты из ArrayBufferView в нативную память.

length должно быть не меньше arrayBufferView.byteLength.

pointer должно указывать на доступную для записи нативную память как минимум с length байтами свободного пространства. Эта функция не выделяет память самостоятельно.

ffi.getRawPointer(source)

Добавлено в: v26.1.0
  • source <Buffer> | <ArrayBuffer>
  • Возвращает: <bigint>

Возвращает адрес необработанной области памяти, в которой хранятся байты под управлением JavaScript.

Это небезопасно и рискованно. Возвращённый указатель может стать недействительным, если связанная с ним память будет отсоединена, изменит размер, будет передана или иным образом станет недействительной. Использование устаревших указателей может привести к повреждению памяти или сбою процесса.

Примечания по безопасности

Модуль node:ffi не отслеживает действительность указателей, владение памятью или время жизни нативных объектов.

В частности:

  • Не читайте данные из освобождённой памяти и не записывайте в неё.
  • Не используйте представления с нулевым копированием после освобождения нативной памяти.
  • Не объявляйте неверные сигнатуры нативных символов.
  • Не отменяйте регистрацию обратных вызовов, пока нативный код может их вызывать.
  • Не вызывайте указатели на обратные вызовы после library.close() или library.unregisterCallback(pointer).
  • Учитывайте, что неопределённое поведение обратного вызова может привести к сбою процесса, неверным результатам или повреждению памяти.
  • Не считайте, что возвращаемое значение-указатель подразумевает владение памятью: необходимость освободить возвращённый адрес полностью зависит от нативного API.

Как правило, предпочитайте копирование значений, если доступ без копирования не является необходимым, и явно определяйте время жизни обратных вызовов и указателей на нативной стороне.

© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/api/ffi.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API