- assert 断言
- async_hooks 异步钩子
- async_hooks/context 异步上下文
- bench 基准测试运行器
- buffer 缓冲区
- C++插件
- C/C++插件(使用 Node-API)
- C++嵌入器
- child_process 子进程
- cluster 集群
- CLI 命令行
- console 控制台
- crypto 加密
- crypto/webcrypto 网络加密
- debugger 调试器
- deprecation 弃用
- dgram 数据报
- diagnostics_channel 诊断通道
- dns 域名服务器
- domain 域
- env 环境变量
- Error 错误
- events 事件触发器
- fs 文件系统
- FFI 外部函数接口
- global 全局变量
- http 超文本传输协议
- http2 超文本传输协议 2.0
- https 安全超文本传输协议
- inspector 检查器
- Intl 国际化
- module 模块
- module/cjs CommonJS 模块
- module/esm ECMAScript 模块
- module/package 包模块
- module/typescript TS 模块
- net 网络
- os 操作系统
- path 路径
- perf_hooks 性能钩子
- permission 权限
- process 进程
- punycode 域名代码
- querystring 查询字符串
- readline 逐行读取
- repl 交互式解释器
- report 诊断报告
- sea 单个可执行应用程序
- sqlite 轻型数据库
- stream 流
- stream/iter 可迭代流
- stream/web 网络流
- string_decoder 字符串解码器
- test 测试
- timers 定时器
- tls 安全传输层
- trace_events 跟踪事件
- tty 终端
- url 网址
- util 实用工具
- v8 引擎
- vfs 虚拟文件系统
- vm 虚拟机
- wasi 网络汇编系统接口
- worker_threads 工作线程
- zlib 压缩
- zlib/iter 可迭代压缩
Node.js v26.11.1 文档
- Node.js v26.11.1
-
目录
- FFI
- 概述
- 类型名称
- 标志性物品
ffi.suffixffi.dlopen(path[, definitions])ffi.dlclose(handle)ffi.dlsym(handle, symbol)- 类:
DynamicLibrarynew DynamicLibrary(path)library.pathlibrary.functionslibrary.symbolslibrary.close()library[Symbol.dispose]()library.getFunction(name, signature)library.getFunctions([definitions])library.getSymbol(name)library.getSymbols()library.registerCallback([signature,] callback)library.unregisterCallback(pointer)library.refCallback(pointer)library.unrefCallback(pointer)
- 调用本地函数
- 调用路径
- 原始内存访问辅助工具
ffi.toString(pointer)ffi.toBuffer(pointer, length[, copy])ffi.toArrayBuffer(pointer, length[, copy])ffi.exportString(string, pointer, length[, encoding])ffi.exportBuffer(buffer, pointer, length)ffi.exportArrayBuffer(arrayBuffer, pointer, length)ffi.exportArrayBufferView(arrayBufferView, pointer, length)ffi.getRawPointer(source)ffi.getCurrentEventLoop()- 安全说明
- FFI
-
导航
- assert 断言
- async_hooks 异步钩子
- async_hooks/context 异步上下文
- bench 基准测试运行器
- buffer 缓冲区
- C++插件
- C/C++插件(使用 Node-API)
- C++嵌入器
- child_process 子进程
- cluster 集群
- CLI 命令行
- console 控制台
- crypto 加密
- crypto/webcrypto 网络加密
- debugger 调试器
- deprecation 弃用
- dgram 数据报
- diagnostics_channel 诊断通道
- dns 域名服务器
- domain 域
- env 环境变量
- Error 错误
- events 事件触发器
- fs 文件系统
- FFI 外部函数接口
- global 全局变量
- http 超文本传输协议
- http2 超文本传输协议 2.0
- https 安全超文本传输协议
- inspector 检查器
- Intl 国际化
- module 模块
- module/cjs CommonJS 模块
- module/esm ECMAScript 模块
- module/package 包模块
- module/typescript TS 模块
- net 网络
- os 操作系统
- path 路径
- perf_hooks 性能钩子
- permission 权限
- process 进程
- punycode 域名代码
- querystring 查询字符串
- readline 逐行读取
- repl 交互式解释器
- report 诊断报告
- sea 单个可执行应用程序
- sqlite 轻型数据库
- stream 流
- stream/iter 可迭代流
- stream/web 网络流
- string_decoder 字符串解码器
- test 测试
- timers 定时器
- tls 安全传输层
- trace_events 跟踪事件
- tty 终端
- url 网址
- util 实用工具
- v8 引擎
- vfs 虚拟文件系统
- vm 虚拟机
- wasi 网络汇编系统接口
- worker_threads 工作线程
- zlib 压缩
- zlib/iter 可迭代压缩
FFI#>
源代码: lib/ffi.js
node:ffi 模块提供了一个实验性的外部函数接口,用于从 JavaScript 加载动态库并调用本地符号。
🌐 The node:ffi module provides an experimental foreign function interface for
loading dynamic libraries and calling native symbols from JavaScript.
此 API 不安全。传递无效指针、使用错误的符号签名或在内存被释放后访问内存可能会导致进程崩溃或内存损坏。
🌐 This API is unsafe. Passing invalid pointers, using an incorrect symbol signature, or accessing memory after it has been freed can crash the process or corrupt memory.
要访问它:
🌐 To access it:
import ffi from 'node:ffi';const ffi = require('node:ffi');
这个模块只在支持 FFI 的构建中通过 node: 方案可用。可以使用 --no-experimental-ffi 标志将其禁用。
🌐 This module is only available under the node: scheme in builds with FFI
support. It can be disabled with the --no-experimental-ffi flag.
在 libffi 提供兼容静态后端的平台上,通过打包的 libffi 可以构建支持 node:ffi 的 Node.js,或者使用 --shared-ffi 配置标志通过共享的 libffi 实现。
非官方的 GN 构建不支持 node:ffi。
🌐 Building Node.js with node:ffi support is available via the bundled libffi on
platforms where libffi provides a compatible static backend, or via a
shared libffi using the --shared-ffi configure flag.
The unofficial GN build does not support node:ffi.
打包的 libffi 不支持以下目标:
🌐 The following targets are not supported by bundled libffi:
s390x.mips、mipsel和mips64el在除 FreeBSD、Linux 和 OpenBSD 之外的目标上。ppc64在 Android、CloudABI、iOS、OpenHarmony、OS/400、Solaris 和 Windows 上。
使用 权限模型 时,除非提供 --allow-ffi 标志,否则 FFI API 会受到限制。
🌐 When using the Permission Model, FFI APIs are
restricted unless the --allow-ffi flag is provided.
概述#>
🌐 Overview
node:ffi 模块暴露了两组 API:
🌐 The node:ffi module exposes two groups of APIs:
- 用于加载库、解析符号和创建可调用 JavaScript 封装器的动态库 API。
- 用于通过指针读取和写入原始值、将指针转换为 JavaScript 字符串、
Buffer实例和ArrayBuffer实例,以及将数据复制回本地内存的原始内存助手。
类型名称#>
🌐 Type names
FFI 签名使用字符串类型名称。
🌐 FFI signatures use string type names.
支持的类型名称:
🌐 Supported type names:
voidcharint8uint8int16uint16int32uint32int64uint64float32float64pointerstringbufferarraybufferfunction
其他拼写
i8给int8u8和bool用于uint8i16给int16u16给uint16i32给int32u32给uint32i64给int64u64给uint64f32和float用于float32f64和double用于float64ptr给pointerstr给string
这些类型名称也作为常量暴露在 ffi.types 上:
🌐 These type names are also exposed as constants on 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)都会作为指针通过本地层传递。
🌐 Pointer-like types (pointer, string, buffer, arraybuffer, and
function) are all passed through the native layer as pointers.
当将 Buffer、ArrayBuffer 或类型化数组的值作为类似指针的参数传递时,Node.js 会在本地调用期间借用它们底层内存的原始指针。调用者必须确保底层存储在整个调用期间保持有效和稳定。
🌐 When Buffer, ArrayBuffer, or typed array values are passed as pointer-like
arguments, Node.js borrows a raw pointer to their backing memory for the
duration of the native call. The caller must ensure that backing store remains
valid and stable for the entire call.
在原生调用处于活动状态时调整大小、传输、分离或以其他方式使该后备存储无效是不受支持且危险的,包括通过可重入的 JavaScript(例如 FFI 回调)进行操作。这样做可能会导致进程崩溃、产生错误输出或损坏内存。
🌐 It is unsupported and dangerous to resize, transfer, detach, or otherwise invalidate that backing store while the native call is active, including through reentrant JavaScript such as FFI callbacks. Doing so may crash the process, produce incorrect output, or corrupt memory.
char 类型遵循平台的 C ABI。在普通 C char 为有符号的的平台上,它的行为像 int8;否则,它的行为像 uint8。
🌐 The char type follows the platform C ABI. On platforms where plain C char
is signed it behaves like int8; otherwise it behaves like uint8.
bool 类型被编组为 8 位无符号整数。传递诸如 0 和 1 的数值;不接受 JavaScript 的 true 和 false。
🌐 The bool type is marshaled as an 8-bit unsigned integer. Pass numeric values
such as 0 and 1; JavaScript true and false are not accepted.
在优化的快速 FFI 调用中,pointer 和 function 参数接受原始指针 bigint 值。对于类似指针的参数 null、undefined、字符串、Buffer、类型数组、DataView 和 ArrayBuffer 的值,会在调用优化后的本地封装器之前,在 JavaScript 端进行转换。
🌐 On optimized Fast FFI calls, pointer and function parameters accept raw
pointer bigint values. For pointer-like parameters, null, undefined,
strings, Buffer, typed array, DataView, and ArrayBuffer values are converted
on the JavaScript side before calling the optimized native wrapper.
当函数的参数或返回类型不适合特定平台的快速跳板时,优化的快速 FFI 调用会退回到另一个 调用路径。快速 FFI 调用最多支持 8 个参数,而且不同架构的寄存器和参数限制也不同:
🌐 Optimized Fast FFI calls fall back to another call path when a function's arguments or return type do not fit the platform-specific fast trampoline. Fast FFI calls support at most 8 total arguments, and the register and argument limits differ per architecture:
| 架构 | 最大整数/指针参数 | 最大浮点参数 | 支持缓冲区形状参数 | 支持缓冲区形状+浮点一起 | 窄(8/16位)返回值 |
|---|---|---|---|---|---|
| AArch64 | 7(如果有缓冲区形状参数则为6) | 8 | 支持 | 不支持 | 支持 |
| x86-64, Linux/macOS(SysV) | 6(如果有缓冲区形状参数则为4) | 8 | 支持 | 不支持 | 支持 |
| x86-64, Windows(Win64) | 3(总参数也限制为3) | 3 | 不支持 | 不适用 | 支持 |
| s390x | 4 | 4 | 不支持 | 不适用 | 不支持 |
| PPC64LE | 7 | 8 | 不支持 | 不适用 | 不支持 |
| LoongArch64 | 7 | 8 | 不支持 | 不适用 | 不支持 |
| RISC-V(64位) | 7 | 8 | 不支持 | 不适用 | 不支持 |
PPC64BE 没有快速调用 trampoline,总是使用通用调用路径。
“缓冲区形状的参数”是指作为指针类参数传递的 Buffer、类型化数组、DataView 或 ArrayBuffer 值。函数的参数或返回类型超过当前平台限制时,会使用其他的 调用路径 之一。
🌐 PPC64BE has no fast-call trampoline and always uses the generic call path.
"Buffer-shaped args" means Buffer, typed array, DataView, or ArrayBuffer
values passed as pointer-like arguments. Functions whose argument or return
types exceed the limits for the current platform use one of the other
call paths instead.
标志性物品#>
🌐 Signature objects
函数和回调使用签名对象来描述。
🌐 Functions and callbacks are described with signature objects.
签名对象可能包含以下属性,这些属性都是可选的:
🌐 Signature objects may contain the following properties, both of which are optional:
return<string> 一个 类型名称,用来指定函数或回调的返回类型。默认值:'void'。arguments<string[]> 一个 类型名称 数组,指定函数或回调的参数类型列表。默认值:[]。
const signature = {
return: 'int32',
arguments: ['int32', 'int32'],
};
ffi.suffix#>
当前平台的本地共享库后缀:
🌐 The native shared library suffix for the current platform:
'dylib'在 macOS 上'so'在类 Unix 平台上'dll'在 Windows 上
这可以用来构建可移植的库路径:
🌐 This can be used to build portable library paths:
const { suffix } = require('node:ffi');
const path = `libsqlite3.${suffix}`;
ffi.dlopen(path[, definitions])#>
加载动态库并解析所请求的函数定义。
🌐 Loads a dynamic library and resolves the requested function definitions.
在 Windows 上不支持传递 null。
🌐 On Windows passing null is not supported.
在已挂载的 虚拟文件系统 内部的 path 是支持的:操作系统的动态加载器无法打开虚拟路径,因此库的字节会从 VFS 读取,并从一个私有的、自动清理的临时映像加载,而 lib.path 会继续报告虚拟路径。真实文件系统上的库不受影响,会直接加载。
🌐 A path inside a mounted virtual file system is supported: the
operating system's dynamic loader cannot open a virtual path, so the
library's bytes are read from the VFS and loaded from a private,
self-cleaning temporary image instead, while lib.path keeps reporting
the virtual path. Libraries on the real file system are unaffected and
load directly.
当省略 definitions 时,functions 会作为一个空对象返回,直到显式解析符号为止。
🌐 When definitions is omitted, functions is returned as an empty object until
symbols are resolved explicitly.
返回的对象包含:
🌐 The returned object contains:
libDynamicLibrary 已加载的库句柄。functions<Object> 所请求符号的可调用封装器。
返回的对象还实现了显式资源管理协议,因此可以与 using 声明一起使用。释放返回的对象会关闭库句柄。
🌐 The returned object also implements the explicit resource management protocol,
so it can be used with the using declaration. Disposing the returned
object closes the library handle.
import { dlopen, suffix } from 'node:ffi';
{
using handle = dlopen(`./mylib.${suffix}`, {
add_i32: { arguments: ['int32', 'int32'], return: 'int32' },
});
console.log(handle.functions.add_i32(20, 22));
} // handle.lib.close() is invoked automatically here.
import { dlopen, suffix } from 'node:ffi';
const { lib, functions } = dlopen(`./mylib.${suffix}`, {
add_i32: { arguments: ['int32', 'int32'], return: 'int32' },
string_length: { arguments: ['pointer'], return: 'uint64' },
});
console.log(functions.add_i32(20, 22));
const { dlopen, suffix } = require('node:ffi');
const { lib, functions } = dlopen(`./mylib.${suffix}`, {
add_i32: { arguments: ['int32', 'int32'], return: 'int32' },
string_length: { arguments: ['pointer'], return: 'uint64' },
});
console.log(functions.add_i32(20, 22));
ffi.dlclose(handle)#>
handleDynamicLibrary
关闭一个动态库。
🌐 Closes a dynamic library.
这相当于调用 handle.close()。
🌐 This is equivalent to calling handle.close().
ffi.dlsym(handle, symbol)#>
从已加载的库中解析符号地址。
🌐 Resolves a symbol address from a loaded library.
这相当于调用 handle.getSymbol(symbol)。
🌐 This is equivalent to calling handle.getSymbol(symbol).
类:DynamicLibrary#>
🌐 Class: DynamicLibrary
表示已加载的动态库。
🌐 Represents a loaded dynamic library.
new DynamicLibrary(path)#>
加载动态库而不急于解析任何函数。
🌐 Loads the dynamic library without resolving any functions eagerly.
在 Windows 上不支持传递 null。
🌐 On Windows passing null is not supported.
安装在已挂载的 虚拟文件系统 内的 path 加载方式与 ffi.dlopen() 相同。
🌐 A path inside a mounted virtual file system loads the same way as
with ffi.dlopen().
const { DynamicLibrary, suffix } = require('node:ffi');
const lib = new DynamicLibrary(`./mylib.${suffix}`);
library.path#>
用于加载库的路径。
🌐 The path used to load the library.
library.functions#>
一个包含先前解析过的函数封装器的对象。
🌐 An object containing previously resolved function wrappers.
library.symbols#>
一个包含先前解析的符号地址作为 bigint 值的对象。
🌐 An object containing previously resolved symbol addresses as bigint values.
library.close()#>
关闭库句柄。
🌐 Closes the library handle.
DynamicLibrary 实现了显式资源管理协议,因此可以使用 using 声明来管理库实例。离开封闭作用域会自动调用 library.close()。
import { DynamicLibrary, suffix } from 'node:ffi';
{
using lib = new DynamicLibrary(`./mylib.${suffix}`);
// Use `lib` here; `lib.close()` is called when the block exits.
}
多次调用 library.close()(或释放库)不会有任何效果。
🌐 Calling library.close() (or disposing the library) more than once is a no-op.
在库关闭之后:
🌐 After a library has been closed:
- 已解析的函数封装器失效。
- 进一步的符号和函数解析会抛出异常。
- 已注册的回调被作废。
关闭一个库并不会使以前导出的回调指针可以安全重用。Node.js 不会跟踪或撤销已经交给本地代码的回调指针。
🌐 Closing a library does not make previously exported callback pointers safe to reuse. Node.js does not track or revoke callback pointers that have already been handed to native code.
如果本地代码在 library.close() 之后或在 library.unregisterCallback(pointer) 之后仍然持有回调指针,调用该指针将具有未定义行为,这是不允许的,也是危险的:它可能导致进程崩溃、产生错误输出或损坏内存。本地代码必须在库关闭之前或回调注销之前停止使用回调地址。
🌐 If native code still holds a callback pointer after library.close() or after
library.unregisterCallback(pointer), invoking that pointer has undefined
behavior, is not allowed, and is dangerous: it can crash the process, produce
incorrect output, or corrupt memory. Native code must stop using callback
addresses before the library is closed or before the callback is unregistered.
从库的某个活动回调中调用 library.close() 是不支持且危险的。回调必须在库关闭之前返回。
🌐 Calling library.close() from one of the library's active callbacks is
unsupported and dangerous. The callback must return before the library is
closed.
library[Symbol.dispose]()#>
调用 library.close()。这允许 DynamicLibrary 实例与 using 声明一起使用,以便在封闭的作用域退出时自动清理。在已关闭的库上,此操作无效。
🌐 Calls library.close(). This allows DynamicLibrary instances to be used with
the using declaration for automatic cleanup when the enclosing scope
exits. It is a no-op on a library that has already been closed.
library.getFunction(name, signature)#>
name<string>signature<Object>- 返回:<Function>
解析一个符号并返回一个可调用的 JavaScript 封装器。
🌐 Resolves a symbol and returns a callable JavaScript wrapper.
返回的函数有一个 .pointer 属性,包含作为 bigint 的原生函数地址。
🌐 The returned function has a .pointer property containing the native function
address as a bigint.
如果相同的符号已经被解析过,再用不同的签名请求它会抛出异常。用相同的签名再次请求它会返回同样的函数,从 library.functions 读取它也是一样。
🌐 If the same symbol has already been resolved, requesting it again with a
different signature throws. Requesting it again with the same signature returns
the same function, as does reading it from library.functions.
const { DynamicLibrary, suffix } = require('node:ffi');
const lib = new DynamicLibrary(`./mylib.${suffix}`);
const add = lib.getFunction('add_i32', {
arguments: ['int32', 'int32'],
return: 'int32',
});
console.log(add(20, 22));
console.log(add.pointer);
library.getFunctions([definitions])#>
当提供 definitions 时,解析每个命名符号并返回一个包含可调用封装器的对象。
🌐 When definitions is provided, resolves each named symbol and returns an
object containing callable wrappers.
当省略 definitions 时,返回库中所有已解析函数的封装器。
🌐 When definitions is omitted, returns wrappers for all functions that have
already been resolved on the library.
library.getSymbol(name)#>
解析符号并返回其本地地址作为 bigint。
🌐 Resolves a symbol and returns its native address as a bigint.
library.getSymbols()#>
- 返回:<Object>
返回一个包含所有先前解析的符号地址的对象。
🌐 Returns an object containing all previously resolved symbol addresses.
library.registerCallback([signature,] callback)#>
signature<Object>callback<Function>- 返回:<bigint>
创建由 JavaScript 函数支持的本地回调指针。
🌐 Creates a native callback pointer backed by a JavaScript function.
当省略 signature 时,回调使用默认的 void () 签名。
🌐 When signature is omitted, the callback uses a default void () signature.
返回值是作为 bigint 的回调指针地址。它可以传递给期望回调指针的本地函数。
🌐 The return value is the callback pointer address as a bigint. It can be
passed to native functions expecting a callback pointer.
const { DynamicLibrary, suffix } = require('node:ffi');
const lib = new DynamicLibrary(`./mylib.${suffix}`);
const callback = lib.registerCallback(
{ arguments: ['int32'], return: 'int32' },
(value) => value * 2,
);
回调受到以下限制:
🌐 Callbacks are subject to the following restrictions:
- 它们必须在创建它们的同一系统线程上被调用。
- 它们不得抛出异常。
- 他们不能食言。
- 它们必须返回与声明的返回类型兼容的值。
- 他们在运行时不得在其拥有的库上调用
library.close()。 - 他们在运行时绝不能注销自己。
从回调内部关闭拥有的库或注销当前正在执行的回调是不支持且危险的。这样做可能会导致进程崩溃、产生错误的输出或损坏内存。
🌐 Closing the owning library or unregistering the currently executing callback from inside the callback is unsupported and dangerous. Doing so may crash the process, produce incorrect output, or corrupt memory.
如果在回调执行期间运行回调的线程被停止,例如被 worker.terminate()、Worker 中的 process.exit() 或主线程退出停止,只有该线程会停止。回调会返回到本地代码但没有值:非空返回值会被初始化为零,所以本地代码会收到 0、false 或空指针。如果本地代码没有处理这种值,例如解引用返回的空指针,可能会导致进程崩溃。
🌐 If the thread running a callback is stopped while the callback executes, for
example by worker.terminate(), by process.exit() in a Worker, or by the
main thread exiting, only that thread stops. The callback returns to native
code without a value: non-void return values are zero-initialized, so native
code receives 0, false, or a null pointer. Native code that does not
handle such a value, for example by dereferencing a returned null pointer, can
crash the process.
library.unregisterCallback(pointer)#>
pointer<bigint>
释放之前使用 library.registerCallback() 创建的回调。
🌐 Releases a callback previously created with library.registerCallback().
调用 library.unregisterCallback(pointer) 以执行当前正在运行的回调是不支持且危险的。回调必须在被取消注册之前返回。
🌐 Calling library.unregisterCallback(pointer) for a callback that is currently
executing is unsupported and dangerous. The callback must return before it is
unregistered.
在 library.unregisterCallback(pointer) 返回后,从本地代码调用该回调指针具有未定义行为,这是不允许的,而且很危险:它可能导致进程崩溃、产生错误的输出或破坏内存。
🌐 After library.unregisterCallback(pointer) returns, invoking that callback
pointer from native code has undefined behavior, is not allowed, and is
dangerous: it can crash the process, produce incorrect output, or corrupt
memory.
library.refCallback(pointer)#>
pointer<bigint>
保持回调在 JavaScript 中被强引用。
🌐 Keeps the callback strongly referenced by JavaScript.
如果回调函数在之前的 library.unrefCallback(pointer) 调用后已经被垃圾回收,会抛出 ERR_INVALID_ARG_VALUE,因为被回收的函数无法再次被引用。
🌐 Throws ERR_INVALID_ARG_VALUE if the callback function has already been
garbage collected after a previous library.unrefCallback(pointer) call, since
a collected function cannot be referenced again.
library.unrefCallback(pointer)#>
pointer<bigint>
允许回调被 JavaScript 弱引用。
🌐 Allows the callback to become weakly referenced by JavaScript.
如果回调函数随后被垃圾回收,后续的本地调用将变为无操作。非空返回值在返回给本地代码之前会被初始化为零。
🌐 If the callback function is later garbage collected, subsequent native invocations become a no-op. Non-void return values are zero-initialized before returning to native code.
如果回调函数已经被垃圾回收,会抛出 ERR_INVALID_ARG_VALUE。
🌐 Throws ERR_INVALID_ARG_VALUE if the callback function has already been
garbage collected.
调用本地函数#>
🌐 Calling native functions
参数转换取决于声明的 FFI 类型。
🌐 Argument conversion depends on the declared FFI type.
对于 8 位、16 位和 32 位整数类型以及浮点类型,请传递与声明类型匹配的 JavaScript number 值。
🌐 For 8-, 16-, and 32-bit integer types and for floating-point types, pass
JavaScript number values that match the declared type.
对于 64 位整数类型(int64 和 uint64),请传入在声明类型范围内的 JavaScript bigint 值或安全整数 number 值。对于 int64,数字必须在 Number.MIN_SAFE_INTEGER 和 Number.MAX_SAFE_INTEGER 之间(含端点)。对于 uint64,数字必须在 0 和 Number.MAX_SAFE_INTEGER 之间(含端点)。这样就可以传递像 buffer.byteLength 这样的缓冲区长度,而无需显式的 BigInt() 转换。对于超出 JavaScript 安全整数范围的整数,请使用 bigint。
🌐 For 64-bit integer types (int64 and uint64), pass JavaScript bigint
values within the declared type's range or safe integer number values.
For int64, numbers must be between Number.MIN_SAFE_INTEGER and
Number.MAX_SAFE_INTEGER, inclusive. For uint64, numbers must be between
0 and Number.MAX_SAFE_INTEGER, inclusive. This allows buffer lengths such
as buffer.byteLength to be passed without an explicit BigInt() conversion.
Use bigint for integers outside JavaScript's safe integer range.
无效的参数,包括小数、NaN、无穷大以及超出这些范围的值,会抛出 ERR_INVALID_ARG_VALUE。64 位整数类型的返回值总是以 bigint 值的形式出现。
🌐 Invalid arguments, including fractional numbers, NaN, infinities, and values
outside these ranges, throw ERR_INVALID_ARG_VALUE. Return values for 64-bit
integer types are always exposed as bigint values.
对于指针类参数:
🌐 For pointer-like arguments:
null和undefined被传递为空指针。string值在调用期间被复制到临时的 NUL 结尾 UTF-8 字符串中。Buffer、类型化数组和DataView实例会传递一个指向其后备内存的指针。ArrayBuffer传递其底层内存的指针。bigint值作为原始指针地址传递。
指针返回值以 bigint 地址的形式显示。
🌐 Pointer return values are exposed as bigint addresses.
调用路径#>
🌐 Call paths
当一个符号通过 ffi.dlopen()、library.getFunction() 或 library.getFunctions() 被解析时,Node.js 会为返回的封装选择三种本地调用路径之一。选择依据是声明的签名、当前平台以及当前进程的能力。这个选择在函数创建时完成,一旦确定就不能配置,也无法从 JavaScript 中观察到。
🌐 When a symbol is resolved through ffi.dlopen(),
library.getFunction(), or library.getFunctions(), Node.js selects
one of three native call paths for the returned wrapper. The selection is based
on the declared signature, on the current platform, and on the capabilities of
the current process. It is made once when the function is created, cannot be
configured, and is not observable from JavaScript.
这些调用路径被设计成对每个 类型名称 接受相同的 JavaScript 值,执行相同的验证,并抛出相同的错误,这样应用就不需要关心某个函数使用了哪条调用路径。它们的区别在于每次调用执行的工作量不同。设置这些路径的目的是为了让常用的签名能够以尽可能低的开销被调用,同时每个支持的签名仍然保持正常工作。
🌐 The call paths are designed to accept the same JavaScript values for each type name, to perform the same validation, and to throw the same errors, so that applications do not need to know which call path a particular function uses. They differ in how much work is done per call. The paths exist so that common signatures can be called with as little overhead as possible while every supported signature keeps working.
Node.js 会按以下顺序尝试调用路径,并使用第一个支持该签名的路径:
🌐 Node.js tries the call paths in the following order and uses the first one that supports the signature:
- 快速 API 调用路径 让优化后的 JavaScript 可以通过生成的每个签名的跳板直接调用本地符号。
- 共享缓冲区调用路径 会通过预先分配的缓冲区传递参数,而不是每次调用时在 JavaScript 和 C++ 边界之间转换每个参数。
- 通用调用路径 会转换 C++ 中的每个参数,然后通过
libffi调用该符号。这个路径支持所有的签名。
贡献者指南 FFI Fast API 内部机制 详细描述了这些调用路径的实现。
🌐 The contributor guide FFI Fast API internals describes the implementation of these call paths in detail.
快速 API 调用路径#>
🌐 Fast API call path
Fast API 调用路径将封装器绑定为 V8 Fast API 函数。当 JavaScript 代码调用这个封装器并被 V8 优化时,调用会从优化后的代码直接进入一个小型的本地跳板——这是 Node.js 在创建函数时为精确签名生成的。这个跳板会把参数移动到本地符号期望的寄存器中并调用它。对于标量入口点,C++ 中没有中间的参数转换。
🌐 The Fast API call path binds the wrapper as a V8 Fast API function. When JavaScript code calling the wrapper is optimized by V8, the call goes from the optimized code straight into a small native trampoline that Node.js generates for the exact signature when the function is created. The trampoline moves the arguments into the registers expected by the native symbol and calls it. For the scalar entry point, there is no intermediate argument conversion in C++.
这个路径上的函数也保持了一个常规的本地入口点。来自 V8 未优化的代码,或者 V8 取消优化的代码的调用,会使用那个入口点,它的行为就像 通用调用路径。这对调用方来说是透明的。
🌐 Functions on this path keep a conventional native entry point as well. Calls from code that V8 has not optimized, or that V8 deoptimizes, use that entry point, which behaves like the generic call path. This is transparent to the caller.
在 trampoline 运行之前,类似指针的参数会在 JavaScript 中准备好:
🌐 Pointer-like arguments are prepared in JavaScript before the trampoline runs:
null和undefined变成了空指针。string的值会被复制到临时的 NUL 结尾 UTF-8 缓冲区中,以便在调用期间使用。Buffer、类型化数组、DataView和ArrayBuffer的值会被转换为原始指针bigint的值,除非下面描述的备用入口点处理它们。bigint的值会保持不变地传递。
对于只带有单个 pointer、buffer 或 arraybuffer 参数的签名,Node.js 还会创建一个备用的快速 API 入口点,可以直接接收 Buffer、类型化数组、DataView 和 ArrayBuffer 值。当参数是这样的值时,JavaScript 封装器会将调用分派给它,而本地辅助程序则直接从后端存储中提取指针,而不是在 JavaScript 中转换值。
🌐 For signatures with a single pointer, buffer, or arraybuffer argument,
Node.js also creates an alternate Fast API entry point that receives Buffer,
typed array, DataView, and ArrayBuffer values directly. The JavaScript
wrapper dispatches to it when the argument is such a value, and a native helper
extracts the pointer from the backing store instead of converting the value in
JavaScript.
只有在满足以下所有条件时,函数才会使用此调用路径:
🌐 A function uses this call path only when all of the following conditions are met:
- 该进程在支持的 64 位架构上运行:AArch64、x86-64、PPC64LE、LoongArch64、RISC-V 64 或 s390x。32 位平台和大端的 PPC64 总是使用另一条调用路径。
- 这个过程可以分配可执行内存。Node.js 每个进程会检查一次是否可以分配内存并标记为可执行。如果检查失败,这条路径在整个进程中都会被禁用。
- 返回类型和任何参数类型都不是
function。 - 这个签名最多有 8 个参数,并且每个参数都能放在当前平台上 trampoline 可用的参数寄存器里。必须传到原生栈上的参数是不支持的。
寄存器限制是特定于平台的。整数和类似指针的参数共享一组寄存器,浮点参数共享另一组。每种架构的限制列在 类型名称 中。
🌐 The register limits are platform-specific. Integer and pointer-like arguments share one set of registers, and floating-point arguments share another. The limits for each architecture are listed in Type names.
未通过这些检查的签名并不是错误。该函数会在下一个支持它的调用路径上被创建。
🌐 A signature that fails any of these checks is not an error. The function is created on the next call path that supports it.
共享缓冲调用路径#>
🌐 Shared buffer call path
共享缓冲区调用路径用于 Fast API 调用路径不支持的签名。当函数创建时,Node.js 会为每个函数分配一个小缓冲区,为返回值提供一个 8 字节的插槽,并为每个参数提供一个 8 字节的插槽。在每次调用时,JavaScript 封装器会验证参数,把它们写入各自的插槽,通过 libffi 调用本地符号而不传递任何 JavaScript 参数,然后从缓冲区读取返回值。这样可以避免在 JavaScript 和 C++ 边界上单独转换每个参数。
🌐 The shared buffer call path is used for signatures that the Fast API call path
does not support. When the function is created, Node.js allocates a small
per-function buffer with one 8-byte slot for the return value and one 8-byte
slot for each argument. On every call, the JavaScript wrapper validates the
arguments, writes them into their slots, invokes the native symbol through
libffi without passing any JavaScript arguments, and then reads the return
value back from the buffer. This avoids converting each argument individually
across the JavaScript and C++ boundary.
当满足以下所有条件时,函数会使用这个调用路径:
🌐 A function uses this call path when all of the following conditions are met:
- 该签名的快速 API 调用路径不可用。
- 主机是小端模式。
- 这个函数签名至少有一个参数。没有参数的函数从共享缓冲区得不到任何好处,会使用另一种调用方式。
在这条路径上支持所有类型的名称,并且参数数量没有限制。
🌐 All type names are supported on this path, and there is no limit on the number of arguments.
类似指针的参数(pointer、string、buffer、arraybuffer 和 function)只有在值是 bigint、null 或 undefined 时才会写入共享缓冲区。当某次调用将字符串、Buffer、类型化数组、DataView 或 ArrayBuffer 传递给类似指针的参数时,该特定调用会交给 通用调用路径 来处理,它会在 C++ 中执行转换。函数本身会保留在共享缓冲区调用路径上,以便后续调用使用。
🌐 Pointer-like arguments (pointer, string, buffer, arraybuffer, and
function) are written to the shared buffer only when the value is a bigint,
null, or undefined. When a call passes a string, Buffer, typed array,
DataView, or ArrayBuffer to a pointer-like parameter, that individual call
is handed off to the generic call path, which performs the conversion in
C++. The function itself stays on the shared buffer call path for later calls.
共享缓冲区对每个函数都是私有的。同一个函数的可重入调用,例如从 FFI 回调中调用,是安全的,因为本地端在调用符号之前会把参数从缓冲区中复制出来。
🌐 The shared buffer is private to each function. Reentrant calls to the same function, for example from an FFI callback, are safe because the native side copies the arguments out of the buffer before invoking the symbol.
通用调用路径#>
🌐 Generic call path
通用调用路径会将每个 JavaScript 参数转换为它在 C++ 中的本地表示,并通过 libffi 调用该符号。它支持 node:ffi 接受的所有签名,并且是其他调用路径在参数验证和错误行为上参考的实现。
🌐 The generic call path converts each JavaScript argument to its native
representation in C++ and calls the symbol through libffi. It supports every
signature that node:ffi accepts and is the reference implementation for the
argument validation and error behavior that the other call paths reproduce.
当 Fast API 调用路径不可用,并且主机是大端模式或签名没有参数时,会直接在这个调用路径上创建一个函数。
🌐 A function is created directly on this call path when the Fast API call path is unavailable and either the host is big-endian or the signature has no arguments.
通用调用路径也处理其他调用路径转交的单独调用,比如 Fast API 函数的未优化或去优化调用点,以及传递非 bigint 指针类值的共享缓冲调用。
🌐 The generic call path also serves individual calls handed off by the other call
paths, such as unoptimized or deoptimized call sites of a Fast API function and
shared buffer calls that pass non-bigint pointer-like values.
使用 library.registerCallback() 创建的回调总是用 libffi 闭包实现的。它们独立于任何函数使用的调用路径。
🌐 Callbacks created with library.registerCallback() are always implemented
with libffi closures. They are independent of the call path used by any
function.
原始内存访问辅助工具#>
🌐 Primitive memory access helpers
以下辅助工具可以在本地指针处读写原始值,也可以选择使用字节偏移:
🌐 The following helpers read and write primitive values at a native pointer, optionally with a byte offset:
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 开始的字节偏移。
🌐 These helpers perform direct memory reads and writes. pointer must be a
bigint referring to valid readable or writable native memory. offset, when
provided, is interpreted as a byte offset from pointer.
getter 辅助函数为 8 位、16 位和 32 位整数类型以及浮点类型返回 JavaScript number 值。它们为 64 位整数类型返回 bigint 值。
🌐 The getter helpers return JavaScript number values for 8-, 16-, and 32-bit
integer types and for floating-point types. They return bigint values for
64-bit integer types.
设置器辅助函数需要一个明确的字节偏移量,并在将提供的 JavaScript 值写入内存之前验证其与目标原生类型的匹配。
对于 setInt64() 和 setUint64(),bigint 值可以直接接受;
数字输入必须是 JavaScript 安全整数范围内的整数。
🌐 The setter helpers require an explicit byte offset and validate the supplied
JavaScript value against the target native type before writing it into memory.
For setInt64() and setUint64(), bigint values are accepted directly;
numeric inputs must be integers within JavaScript's safe integer range.
const {
getInt32,
setInt32,
} = require('node:ffi');
setInt32(ptr, 0, 42);
console.log(getInt32(ptr, 0));
像本模块中的其他原始内存辅助函数一样,这些 API 不跟踪所有权、边界或生命周期。传递无效指针、使用错误的偏移量或通过过期指针写入都可能破坏内存或导致进程崩溃。
🌐 Like the other raw memory helpers in this module, these APIs do not track ownership, bounds, or lifetime. Passing an invalid pointer, using the wrong offset, or writing through a stale pointer can corrupt memory or crash the process.
ffi.toString(pointer)#>
从本地内存读取以 NUL 结尾的 UTF-8 字符串。
🌐 Reads a NUL-terminated UTF-8 string from native memory.
如果 pointer 是 0n,则返回 null。
🌐 If pointer is 0n, null is returned.
此函数不会验证 pointer 是否指向可读内存,也不会验证所指向的数据是否以 \0 结尾。传递无效指针、指向已释放内存的指针或指向没有终止 NUL 的字节的指针,可能会读取无关的内存、导致进程崩溃,或产生截断或乱码输出。
🌐 This function does not validate that pointer refers to readable memory or
that the pointed-to data is terminated with \0. Passing an invalid pointer,
a pointer to freed memory, or a pointer to bytes without a terminating NUL can
read unrelated memory, crash the process, or produce truncated or garbled
output.
const { toString } = require('node:ffi');
const value = toString(ptr);
ffi.toBuffer(pointer, length[, copy])#>
从本地内存创建一个 Buffer。
🌐 Creates a Buffer from native memory.
当 copy 为 true 时,返回的 Buffer 拥有其自己的复制内存。
当 copy 为 false 时,返回的 Buffer 直接引用原始本地内存。
🌐 When copy is true, the returned Buffer owns its own copied memory.
When copy is false, the returned Buffer references the original native
memory directly.
使用 copy: false 是一种零拷贝的应急方法。返回的 Buffer 是一个可写的外部内存视图,因此在 JavaScript 中的写入会直接更新原始本地内存。调用者必须保证:
🌐 Using copy: false is a zero-copy escape hatch. The returned Buffer is a
writable view onto foreign memory, so writes in JavaScript update the original
native memory directly. The caller must guarantee that:
pointer在返回的Buffer的整个生命周期内保持有效。length保持在分配的本地区域内。- 在 JavaScript 仍然使用
Buffer的情况下,没有本地代码会释放或重新利用那块内存。 - 内存保护被遵守。例如,只读内存页不得被写入。
如果这些保证未被满足,读取或写入 Buffer 可能会破坏内存或导致进程崩溃。
🌐 If these guarantees are not met, reading or writing the Buffer can corrupt
memory or crash the process.
ffi.toArrayBuffer(pointer, length[, copy])#>
pointer<bigint>length<number>copy<boolean> 当false,创建一个零拷贝视图。默认:true。- 返回:<ArrayBuffer>
从本地内存创建一个 ArrayBuffer。
🌐 Creates an ArrayBuffer from native memory.
当 copy 为 true 时,返回的 ArrayBuffer 包含复制的字节。
当 copy 为 false 时,返回的 ArrayBuffer 直接引用原始本地内存。
🌐 When copy is true, the returned ArrayBuffer contains copied bytes.
When copy is false, the returned ArrayBuffer references the original
native memory directly.
与 ffi.toBuffer(pointer, length, copy) 描述的相同生命周期和边界要求适用于这里。使用 copy: false 时,返回的 ArrayBuffer 是对外部内存的零拷贝视图,并且只有在该内存保持分配状态、布局未改变且在整个暴露范围内有效时才安全。
🌐 The same lifetime and bounds requirements described for
ffi.toBuffer(pointer, length, copy) apply
here. With copy: false, the
returned ArrayBuffer is a zero-copy view of foreign memory and is only safe
while that memory remains allocated, unchanged in layout, and valid for the
entire exposed range.
ffi.exportString(string, pointer, length[, encoding])#>
将 JavaScript 字符串复制到本地内存并附加一个尾随的 NUL 终止符。
🌐 Copies a JavaScript string into native memory and appends a trailing NUL terminator.
length 必须足够大,以容纳完整的编码字符串以及结尾的 NUL 终止符。对于 UTF-16 和 UCS-2 编码,结尾的终止符使用两个零字节。
pointer 必须指向具有至少 length 字节可用存储空间的可写本地内存。此函数不会自行分配内存。
string 必须是一个 JavaScript 字符串。encoding 必须是一个字符串。
ffi.exportBuffer(buffer, pointer, length)#>
将字节从 Buffer 复制到本地内存。
🌐 Copies bytes from a Buffer into native memory.
length 必须至少为 buffer.length。
pointer 必须指向具有至少 length 字节可用存储空间的可写本地内存。此函数不会自行分配内存。
buffer 必须是一个 Node.js Buffer。
ffi.exportArrayBuffer(arrayBuffer, pointer, length)#>
arrayBuffer<ArrayBuffer>pointer<bigint>length<number>
将字节从 ArrayBuffer 复制到本地内存。
🌐 Copies bytes from an ArrayBuffer into native memory.
length 必须至少为 arrayBuffer.byteLength。
pointer 必须指向具有至少 length 字节可用存储空间的可写本地内存。此函数不会自行分配内存。
ffi.exportArrayBufferView(arrayBufferView, pointer, length)#>
arrayBufferView<ArrayBufferView>pointer<bigint>length<number>
将字节从 ArrayBufferView 复制到本地内存。
🌐 Copies bytes from an ArrayBufferView into native memory.
length 必须至少为 arrayBufferView.byteLength。
pointer 必须指向具有至少 length 字节可用存储空间的可写本地内存。此函数不会自行分配内存。
ffi.getRawPointer(source)#>
source<Buffer> | <ArrayBuffer> | <SharedArrayBuffer> | <ArrayBufferView>- 返回:<bigint>
返回由 JavaScript 管理的字节存储的原始内存地址。
🌐 Returns the raw memory address of JavaScript-managed byte storage.
这是不安全且危险的。如果底层内存被分离、调整大小、转移或以其他方式失效,返回的指针可能会变得无效。使用过时的指针可能导致内存损坏或进程崩溃。
🌐 This is unsafe and dangerous. The returned pointer can become invalid if the underlying memory is detached, resized, transferred, or otherwise invalidated. Using stale pointers can cause memory corruption or process crashes.
ffi.getCurrentEventLoop()#>
- 返回:<bigint>
返回当前线程的 uv_loop_t 的地址,类型为 bigint。
🌐 Returns the address of the current thread's uv_loop_t as a bigint.
返回的地址是当前 Node.js 环境的。在主线程中,这是主线程的事件循环。在工作线程中,这是该工作线程的事件循环。
🌐 The returned address is for the current Node.js environment. In the main thread, this is the main thread event loop. In a worker thread, this is that worker's event loop.
这是不安全也很危险的。返回的指针只在当前环境的生命周期内有效。在环境退出后使用它,或者从假设不同线程或生命周期的本地代码中使用,都可能导致进程崩溃或内存损坏。
🌐 This is unsafe and dangerous. The returned pointer is only valid for the lifetime of the current environment. Using it after the environment exits, or from native code that assumes a different thread or lifetime, can crash the process or corrupt memory.
安全说明#>
🌐 Safety notes
node:ffi 模块不跟踪指针有效性、内存所有权或本地对象的生命周期。
🌐 The node:ffi module does not track pointer validity, memory ownership, or
native object lifetimes.
特别是:
🌐 In particular:
- 不要从已释放的内存中读取或向其写入。
- 在本地内存被释放后,不要使用零拷贝视图。
- 不要为本地符号声明错误的签名。
- 在本地代码可能仍会调用回调时,不要取消注册回调。
- 在
library.close()或library.unregisterCallback(pointer)之后不要调用回调指针。 - 假设未定义的回调行为可能会导致进程崩溃、产生错误的输出或破坏内存。
- 不要假设指针返回值意味着所有权;调用者是否必须释放返回的地址完全取决于本地 API。
一般来说,除非需要零拷贝访问,否则应优先使用复制的值,并在本地端显式保持回调和指针的生命周期。
🌐 As a general rule, prefer copied values unless zero-copy access is required, and keep callback and pointer lifetimes explicit on the native side.