LuaJIT 之 FFI

1. FFI 教程

原文: FFI Tutorial
相关链接:OpenResty 最佳实践之 FFI

加载 FFI 库

FFI 库时默认编译进 LuaJIT 中的,但是不会默认加载或初始化。因此,当需要使用 FFI 库时,需要在 Lua 文件的开头添加如下语句:

local ffi = require("ffi")

访问标准系统函数

如下示例显示了如何访问标准系统函数。

local ffi = require("ffi")
ffi.cdef[[
    void Sleep(int ms);
    int poll(struct pollfd *fds, unsigned long nfds, int timeout);
]]

local sleep
if ffi.os == "Windows" then
    function sleep(s)
        ffi.C.Sleep(s*1000)
    end
else
    function sleep(s)
        ffi.C.poll(nil, 0, s*1000)
    end
end

for i = 1, 160 do
    io.write("."); io.flush()
    sleep(0.01)
end
io.write("\n")

访问 zlib 压缩库

如下示例显示了如果在 Lua 代码中访问 zlib 压缩库。

local ffi = require("ffi")
-- 定义由 zlib 提供的 C 函数
ffi.cdef[[
    unsigned long compressBound(unsigned long sourceLen);
    int compress2(uint8_t *dest, unsigned long *destLen, 
                  const uint8_t *source, unsigned long sourceLen, int level);
    int uncompress(uint8_t *dest, unsigned long *destLen, 
                   const uint8_t *source, unsigned long sourceLen);
]]
-- 加载 zlib 共享库。在 POSIX 系统上,名为 libz.so,通常是预安装的。
-- 因为 ffi.load() 会自动添加缺失的标准前缀/后缀,因此可以简单地加载 "z" 库。
local zlib = ffi.load(ffi.os == "Windows" and "zlib1" or "z")

local function compress(txt)
    -- 首先,通过使用未压缩字符串的长度来调用 zlib.compressBoud 来获取
    -- 压缩缓存区的最大大小.
    local n = zlib.compressBound(#txt)
    -- 分配这个 n 大小的字节缓存区,类型规范中的 [?] 表示可变长度数组(VLA).
    -- 该数组的实际元素个数由 ffi.new 的第二个参数给出.
    local buf = ffi.new("uint8_t[?]", n)
    -- 看上面 compress2 的函数声明可知,destLen 被定义为一个指针。这是因为
    -- 传入的是最大缓存区的大小并返回实际使用的长度.
    -- 在 C 中可以通过传入一个本地变量的地址 (即 &buflen),但是在 Lua 中没有
    -- 地址操作,因此传入的是只有一个元素的数组。
    local buflen = ffi.new("unsigned long[1]", n)
    local res = zlib.compress2(buf, buflen, txt, #txt, 9)
    assert(res == 0)
    -- 将压缩数据作为 Lua 字符串返回,因此使用 ffi.string(),它需要指向
    -- 数据开头和实际长度的指针,这个长度已经通过 buflen 数组返回了
    return ffi.string(buf, buflen[0])
end

local function uncompress(comp, n)
    local buf = ffi.new("uint8_t[?]", n)
    local buflen = ffi.new("unsigned long[1]", n)
    local res = zlib.uncompress(buf, buflen, comp, #comp)
    assert(res == 0)
    return ffi.string(buf, buflen[0])
end

-- Simple test code.
local txt = string.rep("abcd", 1000)
print("Uncompressed size: ", #txt)
local c = compress(txt)
print("Compressed size: ", #c)
local txt2 = uncompress(c, #txt)
assert(txt2 == txt)

为 C Type 定义 Metamethods

local ffi = require("ffi")
ffi.cdef[[
typedef struct { double x, y; } point_t;
]]

local point 
local mt = {
    __add = function(a, b) return point(a.x+b.x, a.y+b.y) end,
    __len = function(a) return math.sqrt(a.x*a.x + a.y*a.y) end,
    __index = {
        area = function(a) return a.x*a.x + a.y*a.y end,
    },
}
point = ffi.metatype("point_t", mt)

local a = point(3, 4)
print(a.x, a.y) --> 3  4
print(#a)       --> 5
print(a:area()) -- 25
local b = a + point(0.5, 8)
print(#b)       --> 12.5

C 和 LuaJIT 相互转化

如下列表显示了如何将常见的 C 语言转化为 LuaJIT FFI:
1382048-20180803113203097-2095644100.png

缓存或不缓存

将库函数缓存在 local 变量或 upvalues 中是一种常见的用法,如下示例

local byte, char = string.byte, string.char
local function foo(x)
    return char(byte(x) + 1)
end

这个可以通过(更快的)直接使用 local 变量或 upvalue 来替换多次哈希表查找。这对于 LuaJIT 来说不是那么重要,因为 JIT 编译器大量优化哈希表查找,甚至能将大部分内容从内循环中提升出来。但是它并不能消除所有这些。

通过 FFI 库调用 C 函数有一点不同。JIT 编译器有特殊的逻辑来消除从 C 库命名空间中解析的函数的所有查找开销。因此,缓存单个 C 函数是没有用的,实际上是适得其反:

local funca, funcb = ffi.funca, ffi.C.funcb -- Not helpful
local function foo(x, n)
    for i = 1, n do funcb(funca(x, i), 1) end
end

这会将它们变成间接调用,并生成更大更慢的机器代码。相反,需要缓存的是命令空间本身并依赖 JIT 编译器来消除查找:

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

这会生成更短更快的代码。因此不要缓存 C 函数,但要缓存命名空间。大多数情况下,命名空间已经位于外部作用域的本地变量中。如来自 local lib = ffi.load(...)。注意,不需要将其复制到函数范围的本地变量中。

2. ffi.* API

词汇表
  • cdecl:抽象 C 类型声明(Lua 字符串)。
  • ctype:C 类型对象。由 ffi.typeof() 返回的一种特殊的 cdata,当被调用时是作为 cdata 的构造函数。
  • ct:一种类型规范,可用于大多数 API 函数。cdecl,ctype 或 cdata 作为模板类型。
  • cb:一个回调对象。这是一个包含特殊函数指针的 C 数据对象。从 C 代码调用此函数会运行关联的 Lua 函数。
  • VLA:通过 [?] 代替元素个数值声明的一个可变长度数组,如 "int[?]"。当创建的时候必须给出元素个数。
  • VLS:可变长度结构体是一个 C 类型的结构体,最后一个元素是 VLA。适用于声明和创建的相同规则。

2.1 声明和访问外部符号

必须首先声明外部符号,然后可以通过索引 C 库命名空间来访问外部符号,该命名空间自动将符号绑定到特定库。

2.1.1 ffi.cdef(def)

声明 C 函数或者 C 的数据结构,数据结构可以是结构体、枚举或者是联合体,函数可以是 C 标准函数,或者第三方库函数,也可以是自定义的函数,注意这里只是函数的声明,并不是函数的定义。声明的函数应该要和原来的函数保持一致。

ffi.cdef[[
typedef struct foo { int a, b; } foo_t;  /* Declare a struct and typedef. */
int dofoo(foo_t *f, int n);              /* Declare an external C function */
]]

注意,外部符号仅被声明,但它们并不受任何特定地址的约束。使用 C 库命名空间实现绑定.此外所有使用的库函数都要对其进行声明。

如何使用自定义的函数?

如下示例,创建一个 myffi.c,内容:

int add(int x, int y)
{
    return x + y;
}

接着在 Linux 下生成动态链接库:

gcc -g -o libmyffi.so -fpic -shared myffi.c

在 LD_LIBRARY_PATH 环境变量中添加生成库的路径:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:your_lib_path

在 Lua 代码中增加如下行:

ffi.load(name, [,global])

ffi.load 会通过给定的 name 加载动态库,返回一个绑定到这个库符号的新的 C 库命名空间,在 POSIX 系统中,如果 global 被设置为 true,这个库符号被加载到一个全局命名空间。另外这个 name 可以是一个动态库的路径,那么会根据路径来查找,否则的话会在默认的搜索路径中去找动态库。在 POSIX 系统中,如果在 name 这个字段中没有写上点符号 .,那么 .so 将会被自动添加进去,例如 ffi.load("z") 会在默认的共享库搜寻路径中去查找 libz.so,在 windows 系统,如果没有包含点号,那么 .dll 会被自动加上。

local ffi = require("ffi")
local myffi = ffi.load("myffi")

ffi.cdef[[
int add(int x, int y); /* don't forget to declare */
]]

local res = myffi.add(1, 2)
print(res)  -- output: 3   Note: please use luajit to run this script.

此外,可以使用 ffi.C (调用 ffi.cdef 中声明的系统函数)来直接调用 add 函数(注:要在 ffi.load 中加上参数 true,如 ffi.load('myffi', true))。

local ffi = require"ffi"
ffi.load('myffi', true)

ffi.cdef[[
int add(int x, int y);   /* don't forget to declare */
]]

local res = ffi.C.add(1, 2)
print(res) -- output: 3   Note: please use luajit to run this script.

2.1.2 ffi.C

这是默认的 C 库命名空间--注意为大写的 C。它绑定到目标系统上的默认符号集或库。这些或多或少与 C 编译器默认提供的相同,而不指定额外的链接库。

在 POSIX 系统中,它绑定到默认或全局命名空间中的符号。这包括可执行文件中的所有导出符号以及加载到全局命名空间中的任意库。这至少包括 libc,libm,libdl(在 Linux 中),libgcc(如果使用 GCC 编译器),以及 LuaJIT 本身提供的 Lua/C API 中的任何导出符号。

2.1.3 clib = ffi.load(name [, global])

这将加载由 name 指定的动态库,并返回一个绑定到其符号的新 C 库命名空间。在 POSIX 系统中,如果 global 为 true,这个库的符号将会加载到全局命名空间中。

如果 name 是路径,该库将会从该路径中加载。否则,name 将以与系统相关的方式进行规范化,并按默认搜索路径来搜索动态库:在 POSIX 系统上,如果 name 不包含 '.',则追加扩展名 .so。此外,如果需要,还会添加库的前缀。所以 ffi.load("z") 在默认的共享库路径中搜索 "libz.so"。

2.2 创建 cdata 对象

2.2.1 ffi.typeof

ctype = ffi.typeof(ct)

创建一个 ctype 对象,会解析一个抽象的 C 类型定义。该函数仅用于解析 cdecl 一次,然后使用生成的 ctype 对象作为构造函数。

local uintptr_t = ffi.typeof("uintptr_t")
local c_str_t = ffi.typeof("const char*")
local int_t = ffi.typeof("int")
local int_array_t = ffi.typeof("int[?]")

2.2.2 ffi.new

如下 API 函数创建 cdata 对象(ctype() 返回 "cdata")。所有创建的对象都是垃圾回收的。

cdata = ffi.new(ct [,nelem] [,init...])
cdata = ctype([nelem,] [init...])

ffi.new 开辟空间,第一个参数为 ctype 对象,ctype 对象最好通过 ctype = ffi.typeof(ct) 构建。

ffi.new 和 ffi.C.malloc 的区别?

如果使用 ffi.new 分配的 cdata 对象指向的内存块是由垃圾回收器 LuaJIT GC 自动管理的,所有不需要用户去释放内存。

如果使用 ffi.C.malloc 分配的空间便不再使用 LuaJIT 自己的分配器了,所以不是由 LuaJIT GC 来管理的,但是,要注意的是 ffi.C.malloc 返回的指针本身所对应的 cdata 对象还是由 LuaJIT GC 来管理的,也就是这个指针的 cdata 对象指向的是用 ffi.C.malloc 分配的内存空间。这个时候,你应该通过 ffi.gc() 函数在这个指针的 cdata 对象上面注册自己的析构函数,这个析构函数里面可以再调用 ffi.C.free,这样的话当 C 指针所对应的 cdata 对象被 LuaJIT GC 管理器垃圾回收的时候,也会自动调用你注册的那个析构函数来执行 C 级别的内存释放。

请尽可能使用最新版本的 LuaJIT,x86_64 上由 LuaJIT GC 管理的内存已经由 1G->2G,虽然管理的内存变大了,但是如果要使用很大的内存,还是用 ffi.C.malloc 来分配会比较好,避免耗尽了 LuaJIT GC 管理内存的上限。

local int_array_t = ffi.typeof("int[?]")
local bucket_v = ffi.new(int_array_t, bucket_sz)

local queue_arr_type = ffi.typeof("lrucache_pureffi_queue_t[?]")
local q = ffi.new(queue_arr_type, size + 1)

2.2.3 ffi.cast

cdata = ffi.cast(ct, init)

创建一个 scalar cdata 对象。

local c_str_t = ffi.typeof("const char*")
local c_str = ffi.case(c_str_t, str)  -- 转换为指针地址

local uintptr_t ffi.typeof("uintptr_t")
tonumber(ffi.cast(uintptr_t, c_str)   -- 转换为数字

2.2.4 ffi.metatype

ctype = ffi.metatype(ct, metatable)

为给定的 ct 创建一个 ctype 对象,并将其与 metatable 相关联。仅允许使用 struct/union 类型,复数和向量。如果需要,其他类型可以封装在 struct 中。

与 metatable 的关联是永久性的,之后不可更改。之后,metatable 的内容和 __index 表(如果有的话)的内容都不能被修改。无论对象如何创建或源自何处,相关地元表都会自动应用于此类型的所有用途。注意,对类型的预定义操作具有优先权(如,声明的字段名称不能被覆盖)。

转载于:https://www.cnblogs.com/jimodetiantang/p/9413168.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用luajit ffi库的相关函数来读取文件路径下的文件名,以下是一个简单的示例代码: ``` local ffi = require("ffi") -- 定义readdir系统调用函数 ffi.cdef[[ typedef struct dirent { long d_ino; off_t d_off; unsigned short d_reclen; unsigned char d_type; char d_name[256]; } dirent; typedef struct DIR { int fd; dirent ent; } DIR; DIR *opendir(const char *name); dirent *readdir(DIR *dirp); int closedir(DIR *dirp); ]] -- 读取文件路径下的文件名 function list_files(path) local dir = ffi.C.opendir(path) local files = {} if dir ~= nil then repeat local entry = ffi.C.readdir(dir) if entry ~= nil then local name = ffi.string(entry.d_name) if name ~= "." and name ~= ".." then table.insert(files, name) end end until entry == nil ffi.C.closedir(dir) end return files end -- 测试 local files = list_files(".") for i, file in ipairs(files) do print(i, file) end ``` 在这个示例中,我们使用ffi.cdef()函数定义了readdir、opendir和closedir系统调用函数。接下来我们定义了一个list_files()函数,该函数使用opendir()函数打开指定路径下的目录,使用readdir()函数读取目录下的文件列表,并使用closedir()函数关闭目录。最后,我们遍历文件列表,将每个文件的文件名添加到一个数组中,并返回该数组。 要使用这个函数,只需将要读取的文件路径传递给list_files()函数,它将返回一个包含该目录下所有文件名的数组。在本例中,我们传递了"."作为路径,它将返回当前目录下的所有文件名。 ### 回答2: 在LuaJIT中使用ffi库读取文件路径下的文件名相对简单,只需要用到ffi库的C语言接口和Lua的文件操作函数就可以完成。以下是一个可以实现该功能的示例代码: ```lua local ffi = require("ffi") -- 定义C函数readdir实现读取文件路径下的文件名 ffi.cdef[[ typedef struct dirent { char d_name[256]; } dirent; typedef struct DIR DIR; DIR* opendir(const char* path); dirent* readdir(DIR* dirp); int closedir(DIR* dirp); ]] -- 定义读取文件路径下文件名的函数 function getFilenames(path) local dir = ffi.C.opendir(path) -- 打开目录 if dir == nil then return nil end local filenames = {} -- 存储文件名的数组 local entry = ffi.C.readdir(dir) -- 读取第一个文件 while entry ~= nil do local filename = ffi.string(entry.d_name) -- 转换为Lua字符串 if filename ~= "." and filename ~= ".." then table.insert(filenames, filename) -- 加入数组 end entry = ffi.C.readdir(dir) -- 读取下一个文件 end ffi.C.closedir(dir) -- 关闭目录 return filenames end -- 示例使用: local path = "./folder" -- 文件路径 local filenames = getFilenames(path) -- 获取文件名数组 if filenames == nil then print("无法打开目录") else for i, filename in ipairs(filenames) do print(filename) end end ``` 以上示例代码使用LuaJITffi库调用了C语言的函数opendir、readdir和closedir来实现读取文件路径下的文件名。首先,通过调用opendir打开指定路径的目录;然后使用readdir循环读取目录中的所有文件,并将文件名加入到一个Lua数组中;最后,调用closedir关闭目录并返回获取到的文件名数组。示例中也处理了当前目录(".")和上层目录(".. ")两个特殊文件名的情况。 你可以将示例代码保存在一个Lua文件中,并根据自己的实际需求修改文件路径,然后运行该Lua文件,即可看到打印出的文件名列表。 ### 回答3: 使用LuaJITffi库可以很方便地调用C函数,进而实现读取文件路径下的文件名的功能。下面是一个示例代码: ```lua -- 引入ffi库 local ffi = require("ffi") -- 定义C函数 ffi.cdef[[ typedef struct { void *d; } DIR; DIR *opendir(const char *filename); void closedir(DIR *dirp); const char *readdir(DIR *dirp); ]] -- 遍历文件路径下的文件名 function getFilenames(filepath) local dir = ffi.C.opendir(filepath) -- 打开目录 if dir ~= nil then local filenames = {} local dirent = ffi.C.readdir(dir) while dirent ~= nil do local name = ffi.string(dirent) -- 排除当前目录和上级目录 if name ~= '.' and name ~= '..' then table.insert(filenames, name) end dirent = ffi.C.readdir(dir) end ffi.C.closedir(dir) -- 关闭目录 return filenames end return nil end -- 测试 local path = "/path/to/directory" local filenames = getFilenames(path) if filenames ~= nil then for i, filename in ipairs(filenames) do print(filename) end else print("Failed to open directory") end ``` 上述代码首先使用ffi.cdef定义了C的DIR结构和相关函数,其中opendir用于打开目录,readdir用于读取目录中的文件名,closedir用于关闭目录。 然后,在getFilenames函数中,我们首先使用ffi.C.opendir打开指定的文件路径,然后循环使用ffi.C.readdir读取目录中的文件名,将有效的文件名加入到filenames表中,最后使用ffi.C.closedir关闭目录。 最后,在测试的部分,我们指定一个文件路径并调用getFilenames函数获取文件名列表,然后遍历打印每个文件名。如果无法打开目录,会打印"Failed to open directory"。 以上便是使用LuaJITffi库实现读取文件路径下的文件名的代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值