上周考虑的 GMP LuaJIT FFI 绑定,最后将 GMP 的绝大部分函数接口导了出来变成了 gmp_ffi,包括整数、浮点、有理数、随机数,以及格式化输出,除了那些 C 直接输出文件包含 FILE * 的部分,后续也可以考虑将标准 C 库的文件操作函数也一并导出来。
另外,还将相关的初始化函数,通过 ffi.gc() 跟 finalizer 关联了起来,就不需要自己做相关的清理工作了。
还有,GMP 的 C 接口,其内部实现,都是带 __gmp 这样前缀的,即便通过 FFI 使用,也很不方便,所以 gmp_ffi 库,将这些接口都重新做了绑定,重命名为类似 mpz_mul() 这样的函数,跟 gmp.h 上通过 C 宏定义出来的接口一样使用。
有了上面的铺垫,一些好玩的东西可以搞起来,比如粗糙的计算素数的程序 test_prime.lua,比如温习一下 RSA 加解密 test_rsa.lua,当前的实现是到处抄的,遥望大学时虽有做这个的大习题,但早就忘了,-.-
贴一下 gmp_ffi.lua 使用的例子吧:
local gmp = require("gmp_ffi")
function gmpffi_test()
local a = gmp.mpz(11111111111)
local b = gmp.mpz("999999999999999999999999999999999")
local c = gmp.mpz("99999")
gmp.mpz_mul(a, a, b)
--gmp.mpz_set_ui(c, 1)
gmp.printf("mpz: a:%Zd, c sign:", a)
print(string.format("%d, odd:%s", gmp.mpz_sgn(c), gmp.mpz_odd(c)))
a = gmp.mpz("ff", 16)
gmp.printf("mpz: value base %Zx\n", a)
a = gmp.mpf(111111111.111111111)
b = gmp.mpf("999999999999999999999999999.99999999999999999999999999999999999")
c = gmp.mpf("9999")
gmp.mpf_mul(a, a, b)
gmp.printf("mpf: %Ff, c sign:", a)
print(gmp.mpf_sgn(c))
a = gmp.mpq(111111111, 111111111)
b = gmp.mpq("9999999999999999999999999999999/99999999999999999999999999999999999999")
c = gmp.mpq(0)
gmp.mpq_div(a, a, b)
gmp.printf("mpq: %Qx, c sign:", a)
print(gmp.mpq_sgn(c))
local rt = gmp.randinit()
local cs = gmp.cstring(64)
gmp.sprintf(cs, "random: %u", gmp.urandomb_ui(rt, 9999999))
print(gmp.tostring(cs))
end
gmpffi_test()
学习使用大数库,比如 GMP,只是使用起来很糟心,比如大整数运算,先要 mpz_init() 最后回收时是 mpz_clear(),没错就是跟 malloc() 和 free() 一样的。就想着能不能稍微改善下易用性,用 C++ 是可以达到目的的,只是牛刀杀鸡,要走编译才能跑的还是太重了,而且 C++ 我也不熟。
就想着不如用 Lua 将其封装起来,之前的经验,是得自己写一个跟 Lua 虚拟机交互数据的接口,一个先进后出塞参数的栈,如果是要凑 table,还要复杂些。但如果使用 LuaJIT 的 FFI 库,就方便多了,具体在 BSD 这边是通过 dlsym 打开共享库,在 LuaJIT 脚本声明 C 接口后,通过 FFI 库直接调用。
能够支持各种 C 的函数接口,比如可变参数,以及各种数据类型,包括可变长度 struct 数据,甚至还有支持 Lua 侧的 gc 对于创建的 cdata 进行 finalizer,让 Lua 的 gc 只管理虚拟机这一侧 C 指针生命周期,API 文档是 http://luajit.org/ext_ffi_api.html。
具体点的例子,比如声明并调用函数,创建一个 struct 结构,以及申请、释放内存的官方例子:
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...); //声明并调用标准 C 库函数
typedef struct { uint8_t red, green, blue, alpha; } rgba_pixel; //struct 结构声明
]]
ffi.C.printf("Hello %s!", "world")
-- 栈上 struct 结构
local img = ffi.new("rgbg_pixel[?]", 3)
img[0].green = 1
-- 创建、并释放外部内存对象
local p = ffi.gc(ffi.C.malloc(n), ffi.C.free)
...
p = nil -- Last reference to p is gone.
-- GC will eventually run finalizer: ffi.C.free(p)
如果是自定义库,库路径需加入 LuaJIT 的 package.cpath 中,使用前 ffi.load('myffi') 一下,比如下面的例子看下 ffi.gc 是不是真的有用:
//#include "stdio.h"
//#include "stdlib.h"
void* test_malloc(int n) {
void *p = malloc(n);
printf("test_malloc %p:%d\n", p, n);
return p;
}
void test_free(void *p) {
printf("test_free %p\n", p);
free(p);
}
先在本目录生成一个共享库:
$ clang -g -o libmyffi.dylib -fpic -shared myffi.c
在 LuaJIT 脚本侧这样使用:
local ffi = require("ffi")
local myffi = ffi.load("myffi")
ffi.cdef[[
void* test_malloc(int n);
void test_free(void *p);
]]
local p = ffi.gc(myffi.test_malloc(10), myffi.test_free)
print( p )
p = nil
会这样输出:
test_malloc 0x7f9962c027c0:10
cdata<void *>: 0x7f9962c027c0
test_free 0x7f9962c027c0
先试一下 GMP 提供的大整数接口:
local ffi = require("ffi")
local gmp = ffi.load("gmp")
ffi.cdef[[
int printf(const char *fmt, ...);
typedef unsigned long long int mp_limb_t;
typedef struct
{
int _mp_alloc; /* Number of *limbs* allocated and pointed
to by the _mp_d field. */
int _mp_size; /* abs(_mp_size) is the number of limbs the
last field points to. If _mp_size is
negative this is a negative number. */
mp_limb_t *_mp_d; /* Pointer to the limbs. */
} __mpz_struct;
typedef const __mpz_struct *mpz_srcptr;
typedef __mpz_struct *mpz_ptr;
typedef __mpz_struct mpz_t[1];
void __gmpz_init(mpz_ptr);
int __gmpz_set_str(mpz_ptr, const char *, int);
void __gmpz_mul(mpz_ptr, mpz_srcptr, mpz_srcptr);
void __gmpz_clear(mpz_ptr);
int __gmp_printf (const char *, ...);
]]
ffi.C.printf("input n1 x %s:\n", "n2")
local arg1, arg2 = ...
if not arg or not arg2 then
os.exit(0)
end
local n1 = ffi.new("mpz_t")
local n2 = ffi.new("mpz_t")
local n3 = ffi.new("mpz_t")
gmp.__gmpz_init(n1)
gmp.__gmpz_init(n2)
gmp.__gmpz_init(n3)
gmp.__gmpz_set_str(n1, arg1, 10)
gmp.__gmpz_set_str(n2, arg2, 10)
gmp.__gmpz_mul(n3, n1, n2)
gmp.__gmp_printf("n1 = %Zd\n", n1)
gmp.__gmp_printf("n2 = %Zd\n", n2)
gmp.__gmp_printf("n1 * n2 = %Zd\n", n3)
gmp.__gmpz_clear(n1)
gmp.__gmpz_clear(n2)
gmp.__gmpz_clear(n3)
输出是:
$ luajit test_ffi.lua 11111111111111111111111 99999999999999999999999999999999
input n1 x n2:
n1 = 11111111111111111111111
n2 = 99999999999999999999999999999999
n1 * n2 = 1111111111111111111111099999999988888888888888888888889
后面再看下怎么封装 GMP 的接口,毕竟这么使用,只是少了一次编译的时间,接口函数还是太复杂。