跳到主要内容

ffi 外部函数接口

ffi 是 Alia 的标准库外部函数接口模块,允许 Alia 与其他编程语言进行互操作,调用外部函数库和系统 API。

引入模块​

import 'ffi' as ffi ~ 导入为变量
import 'ffi' ~ 导入作用域

属性​

available​

检查 FFI 是否可用(koffi 已安装),返回 Boolean。

if ffi.available {
print 'FFI 可用'
}

方法​

importAlias​

将外部语言函数绑定为 Alia 的语法关键字。支持两种形式:

从指定文件导入:

ffi.importAlias from './add.c' named 'C' with signature 'add <int a> <int b> -> int'

从系统库导入:

ffi.importAlias named 'C' with signature 'printf <String format> ... -> int'

导入后,函数可直接作为语法关键字调用:

print add 1 1 ~ 输出 2

可变参数(variadic):签名以 ... 结尾即声明为可变参数函数;调用时固定参数之后 每个可变参数以「类型 + 值」成对给出:

ffi.importAlias named 'C' with signature 'printf <String format> ... -> int'
let n = printf 'Value: %d %s' 'int' 42 'str' 'hello'

类型化指针:<type>* 后缀、<*const T> 与 <*mut T> 前缀均映射为对应元素类型的指针 (int*/*const int → 指向 int 的指针),char*/String* 仍映射为字符串:

ffi.importAlias from './mylib.c' named 'C' with signature 'sum_array <int* arr> <int n> -> int'
ffi.importAlias from './mylib.c' named 'C' with signature 'sum_array <*const int arr> <int n> -> int'

extern​

批量注册外部声明(函数或静态量)。签名形式为函数声明(可含 static 前缀):

~ 函数声明
ffi.extern in 'C' from './mylib.c' with signature 'make_point <int x> <int y> -> Point'

~ 静态量声明
ffi.extern in 'C' from './mylib.c' with signature 'static GLOBAL_COUNTER <int>'

static NAME <type> 通过 lib.symbol(name) 定位符号并在调用时按声明类型解码,调用返回该值。

struct​

定义结构体类型(repr(C)),返回类型名。字段映射为字段名 → 类型:

ffi.struct named 'Point' with *[x: 'int', y: 'int']
ffi.extern in 'C' from './mylib.c' with signature 'point_area <*const Point p> -> int'

字段类型支持定长数组 [T; N](映射为 koffi 数组):

ffi.struct named 'Vec4' with *[v: '[double; 4]']

union​

定义联合体类型(repr(C)),用法与 struct 相同:

ffi.union named 'Value' with *[i: 'int', f: 'float', d: 'double']

enum​

定义枚举类型,成员映射为成员名 → 数值:

ffi.enum named 'Color' with *[RED: 0, GREEN: 1, BLUE: 2]
ffi.extern in 'C' from './mylib.c' with signature 'color_name <Color c> -> String'

proto​

定义命名回调原型类型,签名省略函数名:

ffi.proto named 'IntBin' with signature 'int <int a> <int b> -> int'

exportAlias​

将 Alia 的 Alias 对象导出为外部语言可调用的回调句柄(进程内导出)。需要提供函数签名, 返回的句柄可作为已注册外部函数(importAlias/extern)的指针实参传入:

alias addNumbers <int a> <int b> -> int { a + b }
let cb = ffi.exportAlias in 'C' named addNumbers with signature 'int <int a> <int b> -> int'

open​

加载共享库,返回库句柄。路径省略或为 null 时打开系统符号表。

const lib = ffi.open './mylib.so'

alloc​

按类型分配内存(ffi.alloc T by N 分配 N 个元素,N 省略为 1),返回指针(可作 C 函数的指针实参,或供 ffi.load/ffi.store 读写)。

let p = ffi.alloc 'int' by 3 ~ 3 个 int
let p1 = ffi.alloc 'int' ~ 1 个 int

free​

释放 ffi.alloc 分配的内存。

ffi.free p

load​

读取指针指向的值(ffi.load P as T at I,at I 省略时为第 0 个元素)。

let v = ffi.load p as 'int'
let v3 = ffi.load p as 'int' at 2

store​

向指针指向的位置写入值(ffi.store P as T to V at I,at I 省略为第 0 个元素)。

ffi.store p as 'int' to 10
ffi.store p as 'int' to 30 at 2

address​

获取指针值对应的内存地址(原生地址值),可用于指针的传输与 ffi.load。

let addr = ffi.address p

call​

调用函数指针(ffi.call P with signature S using A?:P 为函数指针,如 ffi.callback/ffi.exportAlias 句柄或返回 void* 的外部函数结果;S 为不含函数名的签名;A 为按序参数数组,可选)。

ffi.extern in 'C' from './mylib.c' with signature 'get_adder <> -> void*'
let fp = call get_adder
let r = ffi.call fp with signature 'int <int a> <int b> -> int' using [3, 5]

invoke​

调用已注册的外部函数。

let result = ffi.invoke 'add_int' with [3, 5]

has​

检查函数是否已注册,返回 Boolean。

let r = ffi.has 'add_int'

build​

编译 C 源文件为共享库,返回编译后文件路径。

const path = ffi.build './mylib.c'

defineType​

注册自定义 C 类型。definition 可为 'opaque'(不透明句柄)、'ptr'/'pointer' (通用指针)或任意 koffi 类型串。

ffi.defineType named 'GtkWidget' as 'opaque'

callback​

由 Alia alias 构造 C 回调,返回回调句柄(可作为已注册外部函数 (importAlias/extern)的指针实参传入)。回调签名省略函数名,仅声明参数与返回类型。

alias onSum <int a> <int b> -> int { a + b }
const cb = ffi.callback with signature 'int <int a> <int b> -> int' named onSum
ffi.importAlias from './cb.c' named 'C' with signature 'invoke_cb <ptr cb> <int a> <int b> -> int'
let r = call invoke_cb [cb, 3, 5] ~ 8

也可基于 ffi.proto 定义的命名原型构造回调(type 指定原型名):

ffi.proto named 'IntBin' with signature 'int <int a> <int b> -> int'
const cb = ffi.callback as 'IntBin' named onSum

回调槽位由 FFI 运行时持有引用(防 GC),无需手动释放。

指针参数回调:回调签名中的 *mut void/*const void 参数在回调内以指针值传入。 比较器类回调用 <Value> 参数接收指针,再以 ffi.load 按元素类型读取其值; 由于分支内返回值在编译到 JS/NodeJS 目标时需显式 return,比较器分支请使用显式返回:

alias cmpInt <Value a> <Value b> -> int {
let x = ffi.load a as 'int'
let y = ffi.load b as 'int'
if x < y { return 0 - 1 } else { if x > y { return 1 } else { return 0 } }
}
ffi.extern in 'C' from './mylib.c' with signature 'qsort <*mut void base> <size_t n> <size_t size> <*mut void cmp> -> void'
let p = ffi.alloc 'int' by 4
ffi.store p as 'int' to 3 at 0
ffi.store p as 'int' to 1 at 1
ffi.store p as 'int' to 4 at 2
ffi.store p as 'int' to 2 at 3
let cb = ffi.callback with signature 'int <*mut void a> <*mut void b> -> int' named cmpInt
call qsort [p, 4, 4, cb]
let first = ffi.load p as 'int' at 0 ~ 1
let last = ffi.load p as 'int' at 3 ~ 4
ffi.free p

pointer​

分配指定类型的一个元素,返回可作指针实参传入 C 函数的 Buffer。

let p = ffi.pointer 'int'

deref​

读取指针指向的值。

let v = ffi.deref p as 'int'

引用位置​

  • 绝对路径:直接使用。
  • 相对路径:基于当前模块目录解析;单文件或代码模式下为执行目录。
  • .c 源文件:先编译为共享库,输出到源文件同级的 ffibuild/ 目录,再加载编译产物。
  • 裸库名:不含路径分隔符的名称交由系统动态链接器解析。
  • 省略或 null:打开系统全局符号表。
  • 查找回退:未显式指定库时,在执行目录及其 ffibuild/ 子目录中查找与函数同名的库文件。

类型映射​

Alia 类型C 类型
intint
int8 ~ int64对应 C 整数类型
uint8 ~ uint64对应 C 无符号整数类型
floatfloat
doubledouble
Numberdouble
str / Stringstr
bool / Booleanbool
gbooleanint(0/1,Alia 侧为 Boolean)
gint / guint / gsize / gssize 等对应整数类型
voidvoid
ptr / pointer / gpointer / Buffer / Value / 未识别类型void *
<type>*(如 int*、double*)指向该元素类型的指针
<*const T> / <*mut T>(如 *const Point)指向该元素类型的指针(只读/可写)
char* / str* / String*str(C 字符串)
自定义类型(defineType)注册的 koffi 类型
struct / union / enum / proto 定义的类型对应的 koffi 复合类型
[T; N](结构体/联合体字段)koffi.array(T, N) 定长数组
...(签名末尾)可变参数(variadic)

示例代码​

import 'ffi' as ffi

~ 从C文件导入函数
ffi.importAlias from './math.c' named 'C' with signature 'add <int a> <int b> -> int'

let result = add 3 5
print result ~ 输出 8

~ 导入系统库函数
ffi.importAlias named 'C' with signature 'abs <int x> -> int'
print abs (-42) ~ 输出 42

~ 可变参数函数
ffi.importAlias named 'C' with signature 'printf <String format> ... -> int'
print printf 'Hi %s' 'str' 'Alia' ~ 输出 7

~ 导出Alia函数为回调句柄
alias multiply <int a> <int b> -> int { a * b }
let cb = ffi.exportAlias in 'C' named multiply with signature 'int <int a> <int b> -> int'

~ 检查函数是否已注册
let hasAdd = ffi.has 'add'
print hasAdd ~ 输出 true

~ 内存分配与 out 参数(divmod 写出商与余数)
ffi.extern in 'C' from './math.c' with signature 'divmod <int a> <int b> <*mut int q> <*mut int r> -> void'
let q = ffi.alloc 'int'
let r = ffi.alloc 'int'
divmod 17 5 q r
let qv = ffi.load q as 'int' ~ 3
let rv = ffi.load r as 'int' ~ 2
ffi.free q
ffi.free r

下一步​