3
这个部分描述了 Lua 的 C API ,也就是宿主程序跟 Lua 通讯用的一组 C 函数。所有的 API 函数按相关的类型以及常量都声明在头文件lua.h
中。
虽然我们说的是“函数”,但一部分简单的 API 是以宏的形式提供的。所有的这些宏都只使用它们的参数一次(除了第一个参数,也就是 lua 状态机),因此你不需担心这些宏的展开会引起一些副作用。
在所有的 C 库中,Lua API 函数都不去检查参数的有效性和坚固性。然而,你可以在编译 Lua 时加上打开一个宏开关来开启luaconf.h
文件中的宏 luai_apicheck
以改变这个行为。
3.1 - 堆栈
Lua 使用一个虚拟栈来和 C 传递值。栈上的的每个元素都是一个 Lua 值(nil,数字,字符串,等等)。
无论何时 Lua 调用 C,被调用的函数都得到一个新的栈,这个栈独立于 C 函数本身的堆栈,也独立于以前的栈。(译注:在 C 函数里,用 Lua API 不能访问到 Lua 状态机中本次调用之外的堆栈中的数据)它里面包含了 Lua 传递给 C 函数的所有参数,而 C 函数则把要返回的结果也放入堆栈以返回给调用者(参见lua_CFunction
)。
方便起见,所有针对栈的 API 查询操作都不严格遵循栈的操作规则。而是可以用一个索引来指向栈上的任何元素:正的索引指的是栈上的绝对位置(从一开始);负的索引则指从栈顶开始的偏移量。更详细的说明一下,如果堆栈有 n 个元素,那么索引 1 表示第一个元素(也就是最先被压入堆栈的元素)而索引 n 则指最后一个元素;索引 -1 也是指最后一个元素(即栈顶的元素),索引 -n 是指第一个元素。如果索引在 1 到栈顶之间(也就是,1 ≤ abs(index) ≤ top
)我们就说这是个有效的索引。
3.2 - 堆栈尺寸
当你使用 Lua API 时,就有责任保证其坚固性。特别需要注意的是,你有责任控制不要堆栈溢出。你可以使用lua_checkstack
这个函数来扩大可用堆栈的尺寸。
无论何时 Lua 调用 C ,它都只保证 LUA_MINSTACK
这么多的堆栈空间可以使用。LUA_MINSTACK
一般被定义为 20 ,因此,只要你不是不断的把数据压栈,通常你不用关心堆栈大小。
所有的查询函数都可以接收一个索引,只要这个索引是任何栈提供的空间中的值。栈能提供的最大空间是通过lua_checkstack
来设置的。这些索引被称作可接受的索引,通常我们把它定义为:
(index < 0 && abs(index) <= top) ||
(index > 0 && index <= stackspace)
注意,0 永远都不是一个可接受的索引。(译注:下文中凡提到的索引,没有特别注明的话,都指可接受的索引。)
3.3 - 伪索引
除了特别声明外,任何一个函数都可以接受另一种有效的索引,它们被称作“伪索引”。这个可以帮助 C 代码访问一些并不在栈上的 Lua 值。伪索引被用来访问线程的环境,函数的环境,注册表,还有 C 函数的 upvalue (参见§3.4)。
线程的环境(也就是全局变量放的地方)通常在伪索引 LUA_GLOBALSINDEX
处。正在运行的 C 函数的环境则放在伪索引LUA_ENVIRONINDEX
之处。
你可以用常规的 table 操作来访问和改变全局变量的值,只需要指定环境表的位置。举例而言,要访问全局变量的值,这样做:
lua_getfield(L, LUA_GLOBALSINDEX, varname);
3.4 - C Closure
当 C 函数被创建出来,我们有可能会把一些值关联在一起,也就是创建一个 C closure ;这些被关联起来的值被叫做 upvalue ,它们可以在函数被调用的时候访问的到。(参见lua_pushcclosure
)。
无论何时去调用 C 函数,函数的 upvalue 都被放在指定的伪索引处。我们可以用lua_upvalueindex
这个宏来生成这些伪索引。第一个关联到函数的值放在lua_upvalueindex(1)
位置处,依次类推。任何情况下都可以用lua_upvalueindex(
n)
产生一个 upvalue 的索引,即使 n 大于实际的 upvalue 数量也可以。它都可以产生一个可接受但不一定有效的索引。
3.5 - 注册表
Lua 提供了一个注册表,这是一个预定义出来的表,可以用来保存任何 C 代码想保存的 Lua 值。这个表可以用伪索引LUA_REGISTRYINDEX
来定位。任何 C 库都可以在这张表里保存数据,为了防止冲突,你需要特别小心的选择键名。一般的用法是,你可以用一个包含你的库名的字符串做为键名,或者可以取你自己 C 代码中的一个地址,以 light userdata 的形式做键。
注册表里的整数健被用于补充库中实现的引用系统的工作,一般说来不要把它们用于别的用途。
3.6 - C中的错误处理
在内部实现中,Lua 使用了 C 的 longjmp
机制来处理错误。(如果你使用 C++ 的话,也可以选择换用异常;参见luaconf.h
文件。)当 Lua 碰到任何错误(比如内存分配错误、类型错误、语法错误、还有一些运行时错误)它都会产生一个错误出去;也就是调用一个 long jump 。在保护环境下,Lua 使用setjmp
来设置一个恢复点;任何发生的错误都会激活最近的一个恢复点。
几乎所有的 API 函数都可能产生错误,例如内存分配错误。但下面的一些函数运行在保护环境中(也就是说它们创建了一个保护环境再在其中运行),因此它们不会产生错误出来:lua_newstate
,lua_close
,lua_load
,lua_pcall
, andlua_cpcall
。
在 C 函数里,你也可以通过调用 lua_error
产生一个错误。
3.7 - 函数和类型
在这里我们按字母次序列出了所有 C API 中的函数和类型。
lua_Alloc
typedef void * (*lua_Alloc) (void *ud,
void *ptr,
size_t osize,
size_t nsize);
Lua 状态机中使用的内存分配器函数的类型。内存分配函数必须提供一个功能类似于realloc
但又不完全相同的函数。它的参数有 ud
,一个由 lua_newstate
传给它的指针; ptr
,一个指向已分配出来或是将被重新分配或是要释放的内存块指针;osize
,内存块原来的尺寸; nsize
,新内存块的尺寸。如果且只有 osize
是零时,ptr
为NULL
。当 nsize
是零,分配器必须返回 NULL
;如果 osize
不是零,分配器应当释放掉ptr
指向的内存块。当 nsize
不是零,若分配器不能满足请求时,分配器返回 NULL
。当nsize
不是零而 osize
是零时,分配器应该和 malloc
有相同的行为。当nsize
和 osize
都不是零时,分配器则应和 realloc
保持一样的行为。 Lua 假设分配器在osize >= nsize
时永远不会失败。
这里有一个简单的分配器函数的实现。这个实现被放在补充库中,由 luaL_newstate
提供。
static void *l_alloc (void *ud, void *ptr, size_t osize,
size_t nsize) {
(void)ud; (void)osize; /* not used */
if (nsize == 0) {
free(ptr);
return NULL;
}
else
return realloc(ptr, nsize);
}
这段代码假设 free(NULL)
啥也不影响,而且realloc(NULL, size)
等价于 malloc(size)
。这两点是 ANSI C 保证的行为。
lua_atpanic
lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf);
设置一个新的 panic (恐慌) 函数,并返回前一个。
如果在保护环境之外发生了任何错误, Lua 就会调用一个 panic 函数,接着调用exit(EXIT_FAILURE)
,这样就开始退出宿主程序。你的 panic 函数可以永远不返回(例如作一次长跳转)来避免程序退出。
panic 函数可以从栈顶取到出错信息。
lua_call
void lua_call (lua_State *L, int nargs, int nresults);
调用一个函数。
要调用一个函数请遵循以下协议:首先,要调用的函数应该被压入堆栈;接着,把需要传递给这个函数的参数按正序压栈;这是指第一个参数首先压栈。最后调用一下lua_call
;nargs
是你压入堆栈的参数个数。当函数调用完毕后,所有的参数以及函数本身都会出栈。而函数的返回值这时则被压入堆栈。返回值的个数将被调整为nresults
个,除非 nresults
被设置成 LUA_MULTRET
。在这种情况下,所有的返回值都被压入堆栈中。 Lua 会保证返回值都放入栈空间中。函数返回值将按正序压栈(第一个返回值首先压栈),因此在调用结束后,最后一个返回值将被放在栈顶。
被调用函数内发生的错误将(通过 longjmp
)一直上抛。
下面的例子中,这行 Lua 代码等价于在宿主程序用 C 代码做一些工作:
a = f("how", t.x, 14)
这里是 C 里的代码:
lua_getfield(L, LUA_GLOBALSINDEX, "f"); /* 将调用的函数 */
lua_pushstring(L, "how"); /* 第一个参数 */
lua_getfield(L, LUA_GLOBALSINDEX, "t"); /* table 的索引 */
lua_getfield(L, -1, "x"); /* 压入 t.x 的值(第 2 个参数)*/
lua_remove(L, -2); /* 从堆栈中移去 't' */
lua_pushinteger(L, 14); /* 第 3 个参数 */
lua_call(L, 3, 1); /* 调用 'f',传入 3 个参数,并索取 1 个返回值 */
lua_setfield(L, LUA_GLOBALSINDEX, "a"); /* 设置全局变量 'a' */
注意上面这段代码是“平衡”的:到了最后,堆栈恢复成原由的配置。这是一种良好的编程习惯。
lua_CFunction
typedef int (*lua_CFunction) (lua_State *L);
C 函数的类型。
为了正确的和 Lua 通讯,C 函数必须使用下列定义了参数以及返回值传递方法的协议: C 函数通过 Lua 中的堆栈来接受参数,参数以正序入栈(第一个参数首先入栈)。因此,当函数开始的时候,lua_gettop(L)
可以返回函数收到的参数个数。第一个参数(如果有的话)在索引 1 的地方,而最后一个参数在索引 lua_gettop(L)
处。当需要向 Lua 返回值的时候,C 函数只需要把它们以正序压到堆栈上(第一个返回值最先压入),然后返回这些返回值的个数。在这些返回值之下的,堆栈上的东西都会被 Lua 丢掉。和 Lua 函数一样,从 Lua 中调用 C 函数也可以有很多返回值。
下面这个例子中的函数将接收若干数字参数,并返回它们的平均数与和:
static int foo (lua_State *L) {
int n = lua_gettop(L); /* 参数的个数 */
lua_Number sum = 0;
int i;
for (i = 1; i <= n; i++) {
if (!lua_isnumber(L, i)) {
lua_pushstring(L, "incorrect argument");
lua_error(L);
}
sum += lua_tonumber(L, i);
}
lua_pushnumber(L, sum/n); /* 第一个返回值 */
lua_pushnumber(L, sum); /* 第二个返回值 */
return 2; /* 返回值的个数 */
}
lua_checkstack
int lua_checkstack (lua_State *L, int extra);
确保堆栈上至少有 extra
个空位。如果不能把堆栈扩展到相应的尺寸,函数返回 false 。这个函数永远不会缩小堆栈;如果堆栈已经比需要的大了,那么就放在那里不会产生变化。
lua_close
void lua_close (lua_State *L);
销毁指定 Lua 状态机中的所有对象(如果有垃圾收集相关的元方法的话,会调用它们),并且释放状态机中使用的所有动态内存。在一些平台上,你可以不必调用这个函数,因为当宿主程序结束的时候,所有的资源就自然被释放掉了。另一方面,长期运行的程序,比如一个后台程序或是一个 web 服务器,当不再需要它们的时候就应该释放掉相关状态机。这样可以避免状态机扩张的过大。
lua_concat
void lua_concat (lua_State *L, int n);
连接栈顶的 n
个值,然后将这些值出栈,并把结果放在栈顶。如果n
为 1 ,结果就是一个字符串放在栈上(即,函数什么都不做);如果 n
为 0 ,结果是一个空串。 连接依照 Lua 中创建语义完成(参见§2.5.4)。
lua_cpcall
int lua_cpcall (lua_State *L, lua_CFunction func, void *ud);
以保护模式调用 C 函数 func
。 func
只有能从堆栈上拿到一个参数,就是包含有 ud
的 light userdata。当有错误时, lua_cpcall
返回和 lua_pcall
相同的错误代码,并在栈顶留下错误对象;否则它返回零,并不会修改堆栈。所有从func
内返回的值都会被扔掉。
lua_createtable
void lua_createtable (lua_State *L, int narr, int nrec);
创建一个新的空 table 压入堆栈。这个新 table 将被预分配narr
个元素的数组空间以及 nrec
个元素的非数组空间。当你明确知道表中需要多少个元素时,预分配就非常有用。如果你不知道,可以使用函数lua_newtable
。