本页内容
外部函数接口
从 Deno 1.13 版本开始,FFI(外部函数接口)API 允许用户使用 Deno.dlopen
调用使用 C ABI(C/C++、Rust、Zig、V 等)编写的原生语言库。
用法 跳转到标题
以下示例展示了如何从 Deno 调用 Rust 函数
// add.rs
#[no_mangle]
pub extern "C" fn add(a: isize, b: isize) -> isize {
a + b
}
将其编译为 C 动态库(在 Linux 上为 libadd.so
)
rustc --crate-type cdylib add.rs
在 C 中,你可以这样写
// add.c
int add(int a, int b) {
return a + b;
}
然后编译它
// unix
cc -c -o add.o add.c
cc -shared -W -o libadd.so add.o
// Windows
cl /LD add.c /link /EXPORT:add
从 Deno 调用库
// ffi.ts
// Determine library extension based on
// your OS.
let libSuffix = "";
switch (Deno.build.os) {
case "windows":
libSuffix = "dll";
break;
case "darwin":
libSuffix = "dylib";
break;
default:
libSuffix = "so";
break;
}
const libName = `./libadd.${libSuffix}`;
// Open library and define exported symbols
const dylib = Deno.dlopen(
libName,
{
"add": { parameters: ["isize", "isize"], result: "isize" },
} as const,
);
// Call the symbol `add`
const result = dylib.symbols.add(35, 34); // 69
console.log(`Result from external addition of 35 and 34: ${result}`);
使用 --allow-ffi
和 --unstable
标志运行
deno run --allow-ffi --unstable ffi.ts
非阻塞 FFI 跳转到标题
在许多情况下,用户可能希望在后台运行 CPU 密集型 FFI 函数,而不会阻塞主线程上的其他任务。
从 Deno 1.15 版本开始,可以在 Deno.dlopen
中将符号标记为 nonblocking
。这些函数调用将在专用阻塞线程上运行,并返回一个解析为所需 result
的 Promise
。
使用 Deno 执行昂贵的 FFI 调用的示例
// sleep.c
#ifdef _WIN32
#include <Windows.h>
#else
#include <time.h>
#endif
int sleep(unsigned int ms) {
#ifdef _WIN32
Sleep(ms);
#else
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&ts, NULL);
#endif
}
从 Deno 调用它
// nonblocking_ffi.ts
const library = Deno.dlopen(
"./sleep.so",
{
sleep: {
parameters: ["usize"],
result: "void",
nonblocking: true,
},
} as const,
);
library.symbols.sleep(500).then(() => console.log("After"));
console.log("Before");
结果
$ deno run --allow-ffi --unstable unblocking_ffi.ts
Before
After
回调 跳转到标题
Deno FFI API 支持从 JavaScript 函数创建 C 回调,以便从动态库回调到 Deno。以下是如何创建和使用回调的示例
// callback_ffi.ts
const library = Deno.dlopen(
"./callback.so",
{
set_status_callback: {
parameters: ["function"],
result: "void",
},
start_long_operation: {
parameters: [],
result: "void",
},
check_status: {
parameters: [],
result: "void",
},
} as const,
);
const callback = new Deno.UnsafeCallback(
{
parameters: ["u8"],
result: "void",
} as const,
(success: number) => {},
);
// Pass the callback pointer to dynamic library
library.symbols.set_status_callback(callback.pointer);
// Start some long operation that does not block the thread
library.symbols.start_long_operation();
// Later, trigger the library to check if the operation is done.
// If it is, this call will trigger the callback.
library.symbols.check_status();
如果 UnsafeCallback
的回调函数抛出错误,该错误将传播到触发回调调用的函数(上面是 check_status()
),并可以在那里捕获。如果返回值的回调抛出异常,则 Deno 将返回 0(指针的空指针)作为结果。
UnsafeCallback
默认情况下不会被释放,因为它会导致使用后释放错误。要正确释放 UnsafeCallback
,必须调用其 close()
方法。
const callback = new Deno.UnsafeCallback(
{ parameters: [], result: "void" } as const,
() => {},
);
// After callback is no longer needed
callback.close();
// It is no longer safe to pass the callback as a parameter.
原生库也可以设置中断处理程序,并让这些处理程序直接触发回调。但是,不建议这样做,因为这可能会导致意外的副作用和未定义的行为。最好任何中断处理程序只设置一个标志,该标志可以稍后像上面使用 check_status()
一样进行轮询。
支持的类型 跳转到标题
以下是 Deno FFI API 目前支持的类型列表。
FFI 类型 | Deno | C | Rust |
---|---|---|---|
i8 |
number |
char / signed char |
i8 |
u8 |
number |
unsigned char |
u8 |
i16 |
number |
short int |
i16 |
u16 |
number |
unsigned short int |
u16 |
i32 |
number |
int / signed int |
i32 |
u32 |
number |
unsigned int |
u32 |
i64 |
number | bigint |
long long int |
i64 |
u64 |
number | bigint |
unsigned long long int |
u64 |
usize |
number | bigint |
size_t |
usize |
isize |
number | bigint |
size_t |
isize |
f32 |
number | bigint |
float |
f32 |
f64 |
number | bigint |
double |
f64 |
void [1] |
undefined |
void |
() |
pointer |
{} | null |
void * |
*mut c_void |
buffer [2] |
TypedArray | null |
uint8_t * |
*mut u8 |
function [3] |
{} | null |
void (*fun)() |
Option<extern "C" fn()> |
{ struct: [...] } [4] |
TypedArray |
struct MyStruct |
MyStruct |
从 Deno 1.25 版本开始,pointer
类型被拆分为 pointer
和 buffer
类型,以确保用户能够利用 Typed Arrays 的优化。从 Deno 1.31 版本开始,pointer
的 JavaScript 表示形式变为不透明指针对象,对于空指针则为 null
。
- [1]
void
类型只能用作结果类型。 - [2]
buffer
类型接受 TypedArrays 作为参数,但当用作结果类型时,它始终返回指针对象或null
,与pointer
类型相同。 - [3]
function
类型在作为参数和结果类型时与pointer
类型的工作方式完全相同。 - [4]
struct
类型用于按值(复制)传递和返回 C 结构体。struct
数组必须按顺序枚举结构体中每个字段的类型。结构体将自动填充:可以通过使用适当数量的u8
字段来定义打包的结构体,以避免填充。仅支持 TypedArrays 作为结构体,结构体始终以Uint8Array
的形式返回。
deno_bindgen 跳转到标题
deno_bindgen
是简化 Rust 编写的 Deno FFI 库的粘合代码生成的官方工具。
它类似于 Rust WASM 生态系统中的 wasm-bindgen
。
以下是一个示例,展示了它的用法
// mul.rs
use deno_bindgen::deno_bindgen;
#[deno_bindgen]
struct Input {
a: i32,
b: i32,
}
#[deno_bindgen]
fn mul(input: Input) -> i32 {
input.a * input.b
}
运行 deno_bindgen
生成绑定。现在可以直接将它们导入到 Deno 中
// mul.ts
import { mul } from "./bindings/bindings.ts";
mul({ a: 10, b: 2 }); // 20
任何与 deno_bindgen
相关的问题都应在 https://github.com/denoland/deno_bindgen/issues 上报告。