了解 OpenResty 的人应该知道,OpenResty 原本的 API 都是基于 C 实现的,不过在新版里都已经改成了基于 FFI 实现的,为什么这么做?因为 FFI 在效率上更有优势,除此以外,FFI 还有一个优点是可以很便利的和 C 交互,我们不妨设想一下,C 语言有那么多成熟的库,通过 FFI,我们可以轻而易举的引入到自己的应用中,何乐而不为呢?
本文通过 Hashids 手把手教你用 OpenResty 里的 FFI。说起 Hashids,它的功能是把一个正整数转换成一个相对更短的唯一 ID,比如把 123456789 转换成 NRv345。基本上主流语言都实现了 Hashids,当然也有 Lua 版本,不过本文即然是讲解 FFI 的,自然不会采用此版本,实际上我们使用的是 C 版本。
下载了 C 版本的 Hashids 源代码之后,第一件事是编译出动态链接库:
gcc -shared -fPIC -o libhashids.so /path/to/hashids.c gcc -dynamiclib -fPIC -o libhashids.dylib /path/to/hashids.c
不同操作系统使用不同的命令:Linux 用前一个,Mac 用后一个。此外还需要把库文件放到系统路径里,同样有操作系统差异,Linux 用 ldconfig,Mac 用 install_name_tool,细节不赘述,让我们直接看看如何通过 FFI 来使用 C 语言的动态链接库,简单说和把大象放冰箱一样,分三步:首先通过 ffi.cdef 添加头文件;然后通过 ffi.load 加载动态链接库,最后把 C 语言的操作步骤翻译成 Lua 代码。看代码吧:
local ffi = require "ffi" ffi.cdef[[ struct hashids_s { char *alphabet; char *alphabet_copy_1; char *alphabet_copy_2; size_t alphabet_length; char *salt; size_t salt_length; char *separators; size_t separators_count; char *guards; size_t guards_count; size_t min_hash_length; }; typedef struct hashids_s hashids_t; void hashids_free(hashids_t *hashids); hashids_t * hashids_init(const char *salt); size_t hashids_encode(hashids_t *hashids, char *buffer, size_t numbers_count, unsigned long long *numbers); size_t hashids_decode(hashids_t *hashids, const char *str, unsigned long long *numbers, size_t numbers_max); size_t hashids_estimate_encoded_size(hashids_t *hashids, size_t numbers_count, unsigned long long *numbers); ]] local id = 123456789 local C = ffi.load("hashids") local hashids = C.hashids_init("this is my salt") local numbers = ffi.new("unsigned long long[1]", id) local size = C.hashids_estimate_encoded_size(hashids, 1, numbers) local buffer = ffi.new("char[?]", size) local length = C.hashids_encode(hashids, buffer, 1, numbers) local hashid = ffi.string(buffer, length) local str = ffi.new("char[?]", #hashid, hashid) numbers = ffi.new("unsigned long long[1]") C.hashids_decode(hashids, str, numbers, -1) C.hashids_free(hashids) ngx.say("id: ", id) -- id: 123456789 ngx.say("hashid: ", hashid) -- hashid: NRv345 ngx.say("decode: ", numbers[0]) -- decode: 123456789ULL ngx.say("decode: ", tonumber(numbers[0])) -- decode: 123456789
在使用 Lua 操作动态链接库的时候,和 C 语言总体保持一致,常见的整数,字符串等数据类型都可以直接使用,唯一需要注意的是 C 语言的指针类型无法直接映射到 Lua 的数据类型,此时的变通做法是通过 ffi.new 声明一个「只有一个元素的数组」。
LuaJIT FFI 不仅可以调用 C 语言,还可以调用其他语言,比如 Go,详情可以参考:
关于 LuaJIT FFI 更多信息,建议浏览官方文档。下面文档也值得一看:
评论前必须登录!
注册