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  进行授权