本文翻译采用 知识共享署名-非商业性使用-相同方式共享 3.0 许可协议 进行许可。
本文翻译节选自 Lua 5.1 Reference Manual ,只包含了描述部分,未包含 API 函数文档。
概述
这部分描述了 Lua 的 C 语言绑定 API,这是一个 C 语言函数集合,用来提供宿主程序和 Lua 之见的信息交换。所有 API 函数和关联的类型、常量都被声明在头文件 lua.h 里。
即使我们使用了“函数”这个术语,许多 API 中的接口仍然以宏的方式提供。所有这样的宏只会精确地将每个参数使用一次(除了第一个参数,第一个参数始终为 Lua State),并且不会产生任何隐藏的副作用。
几乎在所有的 C 语言库中,Lua API 函数都不会检查它们收到参数的有效性和一致性。然而,你可以通过修改 luaconf.h 中的宏定义 luai_apicheck 来改变这个行为。
状态栈
译者注:本文中,带有双引号的“栈”特别指代 Lua State 状态栈。
Lua 使用一个虚拟的“栈”来和 C 语言之间传值和取值。每个栈中的元素都表示一个 Lua 值(nil 空值、number 数值型、string 字符串,诸如此类)。
无论何时,Lua 调用 C 语言时,被调用函数会获得一个新的“栈”,它和前一个“栈”以及 C 语言函数的活动栈相互独立。这个“栈”初始化的时候包含了任何传入 C 语言函数的参数,同时 C 语言函数也在这个“栈”上推入值作为调用者的返回值。详见 lua_CFunction API 的文档。
为了方便,大多数 API 中的查询操作都不会遵循严格的栈访问规则(指严格的先进后出顺序访问,译者注)。相反,调用者可以通过索引引用“栈”上任何位置的元素:一个正数的索引代表“栈”上的绝对位置(从 1 开始);一个负数的索引代表相对“栈”顶的位移位置。更明确来说,如果一个“栈”有 n 个元素,那么索引 1 代表“栈”上的第一个元素(也即第一个被推入栈中的元素)(即栈底,译者注),索引 -1 却代表最后一个元素(也即栈顶元素),索引 -n 代表第一个元素(即栈底,译者注)。如果一个所有的值在 1 到栈顶之间(也即 1 ≤ abs(index) ≤ top ),我们就说这个索引是有效的。
状态栈大小
当你和 Lua API 交互时,你作为开发者有责任自己对一致性问题提供保证。往细里说,你有责任控制“栈”不要溢出。你可以使用 C 语言函数 lua_checkstack 来检视“栈”的大小。
无论何时 Lua 调用 C 语言,都能确保“栈”中有 LUA_MINSTACK (API 中的宏定义,译者注)个位置可供使用。 LUA_MINSTACK 的定义是 20,所以一般情况下你不需要担心“栈”的大小不够你的代码循环推入元素。
大多数查询函数接受去获取有效“栈”空间中的任何值,也即通过 lua_checkstack 检查“栈”的最大大小。这样的索引被称为“可接受索引”。更具有普遍适用意义地,我们给可接受索引一个定义,如下:
注意, 0 在任何时候都不是一个可接受索引。
伪索引
无需额外注意,所有函数都接受被称为“伪索引”的值作为有效的索引。伪索引表示一些可被 C 语言中 Lua API 函数接受,但却不在“栈”中的 Lua 值。伪索引常常被用来访问线程的环境、函数的环境、注册表以及 C 语言函数中的“上值”(upvalue,函数式编程中指闭包函数引用了的外部作用域变量,译者注)(参考 3.4 节)。
线程环境(全局变量存活的地方)始终位于伪索引 LUA_GLOBALSINDEX ,正在运行的 C 语言函数始终位于伪索引 LUA_ENVIRONINDEX 。
如果需要访问和修改全局变量的值,你需要对环境“表”(Lua 中一种类似关联数组的数据结构,译者注)使用常规的表操作。举个例子,访问全局变量,只需要:
C 语言闭包
当一个 C 语言函数被创建出来,就有可能将它和某些值关联起来,从而创建了一个 C 语言的闭包(并非编译原理或离散数学中的闭包,这里特指函数式编程的“词法闭包”,译者注);这些值被称之为“上值”(upvalues),无论绑定的函数在何时被调用,它们都能保证是可访问的。参考 lua_pushcclosure 函数 API 文档。
无论何时,一个 C 语言函数被调用,它的上值都位于一个特殊的伪索引上。这个伪索引由 lua_upvalueindex 宏提供。第一个关联到函数的值位于 lua_upvalueindex(1) 位置,依此类推。任何对 lua_upvalueindex(n) 的访问,n 总是大于当前函数中上值的数量(但不会大于 256),提供一个可被接受(但无效)的特殊索引。
注册表
Lua 提供了一个注册表,一个预定义的表,可以被任何 C 语言代码使用,以便于储存人呢和需要储存的 Lua 值。这个表始终位于伪索引 LUA_REGISTRYINDEX 之上。任何 C 语言库都能将数据存储到这个表中,但存储者应该要小心地选择键名,以和其他库所使用的不同,避免冲突。典型来说,你应该选择一个包含库名的字符串,或者一个内存地址位于你自己的库中的 C 对象所封装的“用户数据”(Lua 对 C 语言指针的一种封装,译者注),来作为你的键名。
注册表中,整型的键名被引用机制所使用,这是 auxiliary 库(Lua API 的一套上层封装工具集,译者注)所实现的。所以不应该将整型键名用作其他用途。
C 语言方面的错误处理
在内部,Lua 使用 C 语言的 longjmp 机制来处理错误。(如果你使用 C++,你也可以使用 C++ 异常,详见 luaconf.h ) 当 Lua 面对任何错误(例如内存分配错误、类型错误、语法错误以及运行时错误)它会将其抛出——也就是做一次跳出(long jump)。在保护环境中,Lua 使用 setjmp 来设置一个恢复指针;从任何错误中都会跳出到最近激活的恢复指针。
大多数 API 函数都能抛出一个错误,例如遇到了内存分配失败。本文档为每一个函数明确声明了可能抛出的错误。
在 C 语言函数中,你可以调用 lua_error 来抛出一个错误。