Lua FFI
Lua FFI
简介
对 libffi 的介绍可以看 [这里],简单来说它就是提供了动态调用任意 C 函数的功能。
FFI库允许调用外部C函数并使用来自纯Lua代码的C数据结构。 FFI库基本上避免了在C中编写繁琐的手动Lua / C绑定的需要。
libffi主要的功能包括两个:
- 动态调用
- 动态定义
动态调用,在运行时动态调用一个函数。
例1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <ffi.h>
int main()
{
ffi_cif cif;
ffi_type *args[1];
void *values[1];
char *s;
ffi_arg rc;
/* Initialize the argument info vectors */
/* 初始化参数信息 */
args[0] = &ffi_type_pointer;
values[0] = &s;
/* Initialize the cif */
/* 初始化cif */
if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1,
&ffi_type_sint, args) == FFI_OK)
{
s = "Hello World!";
ffi_call(&cif, puts, &rc, values);
/* rc now holds the result of the call to puts */
/* values holds a pointer to the function's arg, so to
call puts() again all we need to do is change the
value of s */
s = "This is cool!";
ffi_call(&cif, puts, &rc, values);
}
return 0;
}
例2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int testFunc(int m, int n) {
printf("params: %d %d \n", n, m);
return n+m;
}
int main() {
//拿函数指针
void* functionPtr = dlsym(RTLD_DEFAULT, "testFunc");
int argCount = 2;
//按ffi要求组装好参数类型数组
ffi_type **ffiArgTypes = alloca(sizeof(ffi_type *) *argCount);
ffiArgTypes[0] = &ffi_type_sint;
ffiArgTypes[1] = &ffi_type_sint;
//按ffi要求组装好参数数据数组
void **ffiArgs = alloca(sizeof(void *) *argCount);
void *ffiArgPtr = alloca(ffiArgTypes[0]->size);
int *argPtr = ffiArgPtr;
*argPtr = 1;
ffiArgs[0] = ffiArgPtr;
void *ffiArgPtr2 = alloca(ffiArgTypes[1]->size);
int *argPtr2 = ffiArgPtr2;
*argPtr2 = 2;
ffiArgs[1] = ffiArgPtr2;
//生成 ffi_cfi 对象,保存函数参数个数/类型等信息,相当于一个函数原型
ffi_cif cif;
ffi_type *returnFfiType = &ffi_type_sint;
ffi_status ffiPrepStatus = ffi_prep_cif_var(&cif, FFI_DEFAULT_ABI, (unsigned int)0, (unsigned int)argCount, returnFfiType, ffiArgTypes);
if (ffiPrepStatus == FFI_OK) {
//生成用于保存返回值的内存
void *returnPtr = NULL;
if (returnFfiType->size) {
returnPtr = alloca(returnFfiType->size);
}
//根据cif函数原型,函数指针,返回值内存指针,函数参数数据调用这个函数
ffi_call(&cif, functionPtr, returnPtr, ffiArgs);
//拿到返回值
int returnValue = *(int *)returnPtr;
printf("ret: %d \n", returnValue);
}
}
上面代码展示,只需要知道
- 函数指针(puts)
- 包括参数和返回值信息的函数原型(cif)
- 调用参数(values)
- 保存返回值的指针(rc)
就可以在运行时动态地调用一个函数。
动态定义,运行时增加新的函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <ffi.h>
/* Acts like puts with the file given at time of enclosure. */
/* 新的函数实体 */
void puts_binding(ffi_cif *cif, void *ret, void* args[],
void *stream)
{
*(ffi_arg *)ret = fputs(*(char **)args[0], (FILE *)stream);
}
/* 函数原型 */
typedef int (*puts_t)(char *);
int main()
{
ffi_cif cif;
ffi_type *args[1];
ffi_closure *closure;
void *bound_puts;
int rc;
/* Allocate closure and bound_puts */
/* 给闭包和bound_puts分配内存 */
closure = ffi_closure_alloc(sizeof(ffi_closure), &bound_puts);
if (closure)
{
/* Initialize the argument info vectors */
/* 初始化参数信息 */
args[0] = &ffi_type_pointer;
/* Initialize the cif */
/* 初始化cif */
if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1,
&ffi_type_sint, args) == FFI_OK)
{
/* Initialize the closure, setting stream to stdout */
/* 初始化闭包,设置stream参数为stdout */
if (ffi_prep_closure_loc(closure, &cif, puts_binding,
stdout, bound_puts) == FFI_OK)
{
rc = ((puts_t)bound_puts)("Hello World!");
/* rc now holds the result of the call to fputs */
/* rc 保存了fputs的返回值 */
}
}
}
/* Deallocate both closure, and bound_puts */
/* 释放闭包和bound_puts函数指针 */
ffi_closure_free(closure);
return 0;
}
这个例子,动态定义了一个bound_puts
函数指针,并绑定到puts_binding
函数实体上。
动态定义时使用的api是ffi_prep_closure_loc
函数,需要准备好closure,函数原型(cif),函数实体(puts_binding),透传的userInfo(stdout),函数指针(bound_puts)
closure是ffi_closure
类型对象,用于把其它各个参数关联到一起。
入门 “Hello world”
调用外部C函数
1
2
3
4
5
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("Hello %s!\n", "world")
使用C数据结构
FFI库允许创建和访问C数据结构
简单Lua版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
--cdata_plain.lua
local floor = math.floor
local function image_ramp_green(n)
local img = {}
local f = 255/(n-1)
for i=1,n do
img[i] = { red = 0, green = floor((i-1)*f), blue = 0, alpha = 255 }
end
return img
end
local function image_to_grey(img, n)
for i=1,n do
local y = floor(0.3*img[i].red + 0.59*img[i].green + 0.11*img[i].blue)
img[i].red = y; img[i].green = y; img[i].blue = y
end
end
local N = 400*400
local img = image_ramp_green(N)
for i=1,1000 do
image_to_grey(img, N)
end
运行
1
2
3
4
5
time ./luajit cdata_plain.lua
real 0m4.570s
user 0m4.547s
sys 0m0.020s
FFI版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
--cdata_ffi.lua
local ffi = require("ffi")
ffi.cdef[[
typedef struct { uint8_t red, green, blue, alpha; } rgba_pixel;
]]
local function image_ramp_green(n)
local img = ffi.new("rgba_pixel[?]", n)
local f = 255/(n-1)
for i=0,n-1 do
img[i].green = i*f
img[i].alpha = 255
end
return img
end
local function image_to_grey(img, n)
for i=0,n-1 do
local y = 0.3*img[i].red + 0.59*img[i].green + 0.11*img[i].blue
img[i].red = y; img[i].green = y; img[i].blue = y
end
end
local N = 400*400
local img = image_ramp_green(N)
for i=1,1000 do
image_to_grey(img, N)
end
运行
1
2
3
4
5
time ./luajit cdata_ffi.lua
real 0m0.576s
user 0m0.575s
sys 0m0.001s
性能对比
1
4.570/0.576 = 7.93403
FFI版本的耗时只有简单Lua版本的 1/8 。
进阶 常用的操作
Idiom | C code | Lua code |
---|---|---|
Pointer dereference | x = *p; | x = p[0] |
int *p; | *p = y; | p[0] = y |
Pointer indexing | x = p[i]; | x = p[i] |
int i, *p; | p[i+1] = y; | p[i+1] = y |
Array indexing | x = a[i]; | x = a[i] |
int i, a[]; | a[i+1] = y; | a[i+1] = y |
struct/union dereference | x = s.field; | x = s.field |
struct foo s; | s.field = y; | s.field = y |
struct/union pointer deref. | x = sp->field; | x = s.field |
struct foo *sp; | sp->field = y; | s.field = y |
Pointer arithmetic | x = p + i; | x = p + i |
int i, *p; | y = p - i; | y = p - i |
Pointer difference int p1, p2; | x = p1 - p2; | x = p1 - p2 |
Array element pointer int i, a[]; | x = &a[i]; | x = a+i |
Cast pointer to address int *p; | x = (intptr_t)p; | x = tonumber(ffi.cast(“intptr_t”,p)) |
Functions with outargs void foo(int *inoutlen); | int len = x; foo(&len); y = len; | local len =ffi.new(“int[1]”, x) foo(len) y = len[0] |
Vararg conversions int printf(char *fmt, …); | printf(“%g”, 1.0); printf(“%d”, 1); | printf(“%g”, 1); printf(“%d”,ffi.new(“int”, 1)) |
缓存或者不缓存
普通Lua
1
2
3
4
local byte, char = string.byte, string.char
local function foo(x)
return char(byte(x)+1)
end
这将用(更快)直接使用 localvalue 或 upvalue 来替换几个哈希表查找。
FFI
这种情况与通过FFI库的C函数调用有点不同。缓存单个C函数没有帮助,实际上是反效果
1
2
3
4
local funca, funcb = ffi.C.funca, ffi.C.funcb -- Not helpful!
local function foo(x, n)
for i=1,n do funcb(funca(x, i), 1) end
end
应该缓存命名空间
1
2
3
4
local C = ffi.C -- Instead use this!
local function foo(x, n)
for i=1,n do C.funcb(C.funca(x, i), 1) end
end
本文由作者按照 CC BY 4.0 进行授权