0%

Lua中与C中的数据共享

Skynet 中,经常会遇到在 Lua 服务间进行数据的传输,要将数据从 C 层共享到 Lua 层去,实际上有一些需要注意的地方。再加上 Lua 的 GC 问题一不小心就犯错。

通常,我们要将一个数据推送到 Lua 中去,那么会用到他的 push 系列函数。

push 系列函数

对于栈上参数的变化,我们会用一个三元组来表示 [-o, +p, x] 来表示:

  • o 表示从栈上弹出的元素数量
  • p 表示压到栈上的元素数量,通常,函数会将返回结果都压到栈上去
  • x 表示这个函数会不会抛出错误
    • - 表示永不会抛出错误
    • m 表示会可能会抛出 out-of-memory 错误和运行 __gc__ 元方法产生的错误
    • e 表示会抛出错误
    • v 表示有目的的抛出错误

lua_pushboolean

[-0, +1, –] void lua_pushboolean (lua_State *L, int b); 压一个 boolean 值 b 到栈上

lua_pushcclosure

[-n, +1, m]
void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);

将一个新的 C 闭包压到栈上。这个函数接受一个 C 函数的指针作为参数,然后将一个 Lua 函数类型的值压到栈上,当调用这个 Lua 函数类型的值时,就会调用对应的 C 函数。参数 n 指明这个函数会有多少个上值。

任何想要被 Lua 调用的函数必须符合正确的形式来接收它的参数和返回其结果。

当一个 C 函数被创建的时候,可能会有一些值与它相关,他们被叫做上值;任何时候调用这些函数,这些上值都可以访问到。这种关联就叫做 C 闭包。为了创建一个闭包,首先上值的初始值必须压到栈上。(当有多个上值的时候,第一个上值第一个压上去)。接着就会调用 lua_pushcclosure 来创建函数且压到栈上,n 来说明有多少个上值与其关联,lua_pushcclosure 会从栈上弹出这些上值。

上值数两 n 最大 255.

n 为 0 时,建立一个 light C 函数,它仅仅只是一个指向 C 函数的指针。这种情况下,就不会抛出内存错误。

lua_pushcfunction

[-0, +1, –]
void lua_pushcfunction (lua_State *L, lua_CFunction f);

将 C 函数压到栈上,这个函数与没有上值的 lua_pushcclosure 一样。

lua_pushfstring

[-0, +1, v]
const char *lua_pushfstring (lua_State *L, const char *fmt, ...);

将一个格式化的字符串压到栈上,同时返回指向这个字符串的指针。它和 ISO 的 C 函数 sprintf 相似,但有两个很大的不同:

  1. 首先,我们不需要为结果分配内存;结果是 Lua 字符串,Lua 会进行内存分配(也会通过垃圾回收器回收内存)。
  2. 然后,转换指示符是受限的。没有关于 标志,宽度,精度的指示符。转换指示符只能是:
    • %% 插入字符 %
    • %s 插入一个以空字符结束二字符串,没有大小限制
    • %f 插入一个 lua_Number
    • %I lua_Interger
    • %p 指针
    • %d int
    • %c 单字节的字符
    • %U 作为 UTF-8 字节序列的长整型

这个函数会在内存溢出或给了一个无效的指示符的时候抛出错误。

lua_pushglobaltable

[-0, +1, –]
void lua_pushglobaltable (lua_State *L);

全局环境压到栈上。

lua_pushinteger

[-0, +1, –]
void lua_pushinteger (lua_State *L, lua_Integer n);

将一个值为 n 的 integer 压到栈上。

lua_pushlightuserdata

[-0, +1, –]
void lua_pushlightuserdata (lua_State *L, void *p);

将一个 light userdata 压到栈上。

Userdata 代表 Lua 中的 C 值。一个 light userdata 代表了一个指针,一个 void *。它是一个值(和一个 number 一样):我们不需要创建它,它没有单独的元表,也不会被回收(因为它从没有创建)。一个 light userdata 和其他所有有相同 C 地址的 light userdata 相等。

lua_pushliteral

[-0, +1, m]
const char *lua_pushliteral (lua_State *L, const char *s);

这个和 lua_pushstring 相同,但只在 s 是一个字面字符串的时候使用(Lua 可能会优化这种情况),

lua_pushlstring

[-0, +1, m]
const char *lua_pushlstring (lua_State *L, const char *s, size_t len);

将指针 s 指向的字符串及字符串的长度len 压到栈上。 Lua 会新建或者重用这个字符串的副本,所以位于 s 处的内存可以马上释放或者重用。这个字符串可以包含任何数据,即使是内嵌的空字符。

返回一个指向 Lua 内部副本的指针。

lua_pushnil

[-0, +1, –]
void lua_pushnil (lua_State *L);

压应该 nil 值到栈上。

lua_pushnumber

[-0, +1, –]
void lua_pushnumber (lua_State *L, lua_Number n);

压应该 float ,值为 n 到 栈上。

lua_pushstring

[-0, +1, m]
const char *lua_pushstring (lua_State *L, const char *s);

压一个空字符结束的字符串到栈上。Lua 会为这个字符串制作副本。返回制作的副本字符串的指针。

s 是 NULL,那就压入 nil 返回 NULL

lua_pushthread

[-0, +1, –]
int lua_pushthread (lua_State *L);

将 L 代表的线程压到栈上。如果线程是 Lua State 的主线程,那么返回 1

lua_pushvalue

[-0, +1, –]
void lua_pushvalue (lua_State *L, int index);

将栈上,指定索引处的值,制作一个副本,压到栈上。

lua_pushvfstring

[-0, +1, v] const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp);

与 lua_pushfstring 相等,只是它接受的是一个 va_list 参数而不是可变数量的参数。

lua_newuserdata 5.4之前

[-0, +1, m]
void *lua_newuserdata (lua_State *L, size_t size);

分配一个 size 大小的内存,将此块内存的地址作为一个新的完整的 usedata 压到栈上,然后分配的地址。宿主程序可以随意使用这块内存。

lua_newuserdatauv 5.4

[-0, +1, m]
void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue);

新建一个完全的 userdata,并压到栈上;这个 userdata 会与 nuvalue 个 Lua 值关联,他们被叫做用户值;并加上 size 字节的原始内存。用户值可以通过 API lua_setiuservalue, lua_getiuservalue 处理。

返回内存块的地址。

lua_touserdata

[-0, +0, –]
void *lua_touserdata (lua_State *L, int index);

如果索引 index 处的值是一个完整的 userdata,返回其内存块地址。如果是 light userdata,返回指针。否则就返回 NULL。

skynet 的使用

当我们在 Lua 服务内发送数据的时候,对于发送的多个值,我们需要将其调用 skynet 的序列化 API,把他序列化一块内存中去,然后以 light userdata 的形式发到其他服务,也就是指针。 服务的收到方,同样需要调用反序列化函数来将其转换成 Lua 的值。

C 库相关API

luaL_newlib

[-0, +1, m] void luaL_newlib (lua_State *L, const luaL_Reg l[]);

建立一个新表,并将数组 l 中的所有函数都注册进去。它实际上实现为一个宏:

(luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))

注意:l 必须是一个数组,而不能是指向数组的指针。

luaL_newlibtable

[-0, +1, m] void luaL_newlibtable (lua_State *L, const luaL_Reg l[]);

建立一个新表,其尺寸被优化来可以存储数组 l 中的所有项目(但并不真正的进行存储)。它的目的是为了和luaL_setfuncs (see luaL_newlib) 配合使用。

他也是被实现为宏。数组不能是一个指向数组的指针。

#define luaL_newlibtable(L,l)	\
lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)

luaL_setfuncs

[-nup, +0, m] void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup);

将数组 l 中的所有函数注册到栈顶的表中(在表下面的是他们的上值)。

nup 非0,所有的函数都会有 nup 个上值,上值被栈上库表之下的值所初始化。在注册完毕后上值会弹出。