local ffi = require("ffi")模块会导出函数以及以下三个值:
ffi.VERSIONffi.nullptrffi.C
使用 ffi.cdef 提供 C 声明。
ffi.cdef([[
struct Point {
int x;
int y;
};
typedef struct Point Point;
typedef int (*callback_t)(int);
int puts(const char *s);
]])- 本项目支持的基础 C 标量类型。
typedef声明。struct与union声明(包含嵌套/匿名成员)。__attribute__((packed))解析。- 函数声明与函数指针类型。
- 数组声明符,包括在
ffi.new类型字符串中使用?的柔性形式。
- 声明是可叠加的。
- 已知符号重复定义会报错。
cdef仅用于声明(不支持函数定义)。
以下内置基础 C 类型默认可用:
void bool char short int long float double
int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t
ino_t dev_t gid_t mode_t nlink_t uid_t off_t pid_t size_t ssize_t useconds_t suseconds_t blksize_t blkcnt_t time_t
ffi.C 是 ffi 模块默认加载系统 C 库后返回的对象。你可以通过该对象访问已经声明的系统 C 函数。
ffi.cdef([[ int puts(const char *s); ]])
ffi.C.puts("hello")加载动态库并返回库对象。
local lib = ffi.load("./libdemo.so")
local lib_global = ffi.load("./libdemo.so", true)path:动态库路径。global(可选):为 true 时使用全局符号可见性加载。
库对象行为:
lib.symbol会查找已声明函数。- 若缺少声明:会报“缺少声明”错误。
- 若有声明但库中没有符号:会报“未定义函数”错误。
签名:
ffi.new(ct [, init])ct 可以是:
- C 类型字符串。
- 由
ffi.typeof返回的 ctype 对象。
local i = ffi.new("int", 123)
local p = ffi.new("struct Point", {1, 2})
local a = ffi.new("int[4]", {1, 2})
local b = ffi.new("int [?]", 8, {1, 2, 3})初始化语义:
- 默认零初始化。
- 传入初始化值时,只接受一个初始化参数。
- 数组与结构体初始化支持 Lua table。
无效构造:
- 不能创建
void值。 - 不能创建函数值。
签名:
ffi.cast(ct, value)常见用法:
local p = ffi.cast("int *", 0x1234)
local n = ffi.cast("int", p)要创建回调函数指针,可将 Lua 函数 cast 到函数指针类型:
local cb = ffi.cast("int (*)(int)", function(x)
return x * 2
end)重要行为:
- 函数指针 cast 期望传入 Lua function。
- 回调生命周期由返回的 cdata 对象跟踪。
返回规范化的 ctype 对象。
local t = ffi.typeof("struct Point")当 obj 与 ct 是完全相同的规范化 ctype 时返回 true。
local p = ffi.new("int", 1)
assert(ffi.istype("int", p))返回类型或 cdata 值的字节大小。
local sz = ffi.sizeof("struct Point")返回记录类型字段偏移。
local off = ffi.offsetof("struct Point", "y")字段不存在时返回 nil。
返回指向底层存储的指针 cdata。
local v = ffi.new("int", 7)
local pv = ffi.addressof(v)
pv[0] = 99将数值标量 cdata 转成 Lua number/integer。
local v = ffi.new("int", 42)
assert(ffi.tonumber(v) == 42)对非数值 cdata(例如 record)返回 nil。
将字符指针/数组(或指定长度的原始内存)转换为 Lua 字符串。
local s = ffi.new("char[16]", "hello")
assert(ffi.string(s) == "hello")
assert(ffi.string(s, 3) == "hel")传入显式 len 时,可将 pointer/array/record 内存按字节读取。
向目标 cdata 拷贝内存。
local dst = ffi.new("char[16]")
ffi.copy(dst, "abc") -- 同时写入 NUL 终止符
ffi.copy(dst, "xyz", 2) -- 原样拷贝 2 个字节memset 风格填充。
local buf = ffi.new("uint8_t[4]")
ffi.fill(buf, 4, 0x5a)签名:
old = ffi.errno([new_errno])- 返回当前
errno。 - 传入参数时会设置
errno,并返回旧值。
local prev = ffi.errno()
ffi.errno(2)
ffi.errno(prev)为 cdata 绑定或移除 Lua 析构回调。
local p = ffi.gc(ffi.C.malloc(128), ffi.C.free)
-- 移除析构回调
ffi.gc(p, nil)返回值仍是同一个 cdata 对象。
为记录类型关联元方法表。
ffi.cdef([[
struct Point { int x; int y; };
]])
local Point = ffi.metatype(ffi.typeof("struct Point"), {
__tostring = function(self)
return string.format("(%d,%d)", self.x, self.y)
end,
__index = {
add = function(self, dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end,
},
})
local p = ffi.new(Point, {1, 2})
p:add(10, 20)运行时使用的元类型钩子:
__index__tostring
- 记录 cdata:
obj.field - 指针/数组 cdata:
obj[index] - 指向记录的指针支持按字段名访问。
对 const 限定目标的赋值会被拒绝。
- 指针 cdata 比较指针值。
- 数值标量 cdata 按转换后的 Lua 数值比较。
- 与
nil的比较可用于空指针判断。
已声明的 C 函数会变成可调用 cdata。
ffi.cdef([[ int puts(const char *s); ]])
ffi.C.puts("hi")支持可变参数声明。
数组支持 #cdata,返回元素个数。
版本字符串。
规范的空指针 cdata。
空指针比较示例:
assert(ffi.nullptr == ffi.cast("void *", nil))local ffi = require("ffi")
ffi.cdef([[
struct student {
int age;
char name[0];
};
void *malloc(size_t size);
void free(void *ptr);
int printf(const char *format, ...);
]])
local p = ffi.gc(ffi.C.malloc(ffi.sizeof("struct student") + 32), ffi.C.free)
local st = ffi.new("struct student *", p)
st.age = 18
ffi.copy(st.name, "alice")
ffi.C.printf("name=%s age=%d\n", st.name, st.age)- 先声明再使用:函数调用需要匹配的
cdef原型。 - 保持回调存活:只要 C 侧仍可能调用,就要持有回调 cdata。
- 明确所有权:为堆内存使用
ffi.gc。 - 正确使用
ffi.string:- NUL 结尾字符缓冲区用无长度形式;
- 原始字节读取用带长度形式。
- 使用
ffi.istype时应基于规范化类型进行比较。