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 类型 |
|---|---|
int | int |
int8 ~ int64 | 对应 C 整数类型 |
uint8 ~ uint64 | 对应 C 无符号整数类型 |
float | float |
double | double |
Number | double |
str / String | str |
bool / Boolean | bool |
gboolean | int(0/1,Alia 侧为 Boolean) |
gint / guint / gsize / gssize 等 | 对应整数类型 |
void | void |
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