手把手教你用OpenResty里的FFI

2021-12-14 09:01:50 浏览数 (1)

了解 OpenResty 的人应该知道,OpenResty 原本的 API 都是基于 C 实现的,不过在新版里都已经改成了基于 FFI 实现的,为什么这么做?因为 FFI 在效率上更有优势,除此以外,FFI 还有一个优点是可以很便利的和 C 交互,我们不妨设想一下,C 语言有那么多成熟的库,通过 FFI,我们可以轻而易举的引入到自己的应用中,何乐而不为呢?

本文通过 Hashids 手把手教你用 OpenResty 里的 FFI。说起 Hashids,它的功能是把一个正整数转换成一个相对更短的唯一 ID,比如把 123456789 转换成 NRv345。基本上主流语言都实现了 Hashids,当然也有 Lua 版本,不过本文即然是讲解 FFI 的,自然不会采用此版本,实际上我们使用的是 C 版本。

下载了 C 版本的 Hashids 源代码之后,第一件事是编译出动态链接库:

代码语言:javascript复制
➜ 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 代码。看代码吧:

代码语言:javascript复制
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,详情可以参考:

  • Calling Go Functions from Other Languages
  • 在 LuaJIT 中调用 Go 函数

关于 LuaJIT FFI 更多信息,建议浏览官方文档。下面文档也值得一看:

  • LuaJIT FFI 介绍,及其在 OpenResty 中的应用(上)
  • LuaJIT FFI 介绍,及其在 OpenResty 中的应用(下)

此外,luapower 上能找到不少使用 FFI 的代码,建议多看看。

0 人点赞