(接上篇)
-------------------
5.8 执行 Lua 代码
-------------------
一个宿主程序可以执行写在文件中或在字符串中的 Lua 块,使用下面的函数:
int lua_dofile (lua_State *L, const char *filename); int lua_dostring (lua_State *L, const char *string); int lua_dobuffer (lua_State *L, const char *buff, size_t size, const char *name);
这些函数返回 0 表示成功,或者失败时返回一个错误码:
> LUA_ERRRUN - 执行模块时出错。
> LUA_ERRSYNTAX - 预编译时语法错误。
> LUA_ERRMEM - 内存分配错误。对于此类错误,Lua 不会调用 _ERRORMESSAGE (参见 4.7 节)。
> LUA_ERRERR - 执行 _ERRORMESSAGE 时出错。对于此类错误,Lua 不会再次调用 _ERRORMESSAGE ,以免死循环。
> LUA_ERRFILE - 打开文件错误(只针对 lua_dofile)。在这种情况下,你可能想要检查 errno,调用 strerror,或者调用 perror 去告诉用户哪里出了问题。
这些常量定义在 lua.h 中。
当通过 NULL 参数调用时,lua_dofile 执行标准输入流。lua_dofile 和 lua_dobuffer 都可以执行预编译的块。它们自动检测出块是文本或者二进制,并相应地加载它(参见程序 luac)。lua_dostring 只执行文本格式的源代码。
lua_dobuffer 的第三个参数是块的名字,它用在错误信息和调试信息中。如果名字是 NULL,Lua 会给块一个默认的名字。
这些函数把块最终返回的结果压栈。一个块可以返回任意数量的值;Lua 保证这些值可以存放到栈空间中,但是调用之后就需要你自己维护栈空间了。如果你需要压栈其它的元素在调用这些函数之后,并且你想安全地使用栈空间,你必须要么通过 lua_stackspace 来检查栈空间,要求从栈同移除元素(如果你不需要它们的话)。例如,下面的代码加载一个文件中的块并且丢弃所有的这个块返回的结果,把栈恢复为调用之间的状态:
{ int oldtop = lua_gettop(L); lua_dofile(L, filename); lua_settop(L, oldtop); }
-------------------
5.9 操纵 Lua 中的全局变量
-------------------
可以使用下面的函数读取 Lua 全局变量的值:
void lua_getglobal (lua_State *L, const char *varname);
它将会把给定变量的值压栈。在 Lua 中,这个函数可能触发一个标签方法 ``getglobal‘‘(参见 4.8 节)。为了读取全局变量真正的值,而不唤起任何标签方法,在全局表上使用 lua_rawget(参见下面)。
保存一个值到全局变量,调用
void lua_setglobal (lua_State *L, const char *varname);
它出栈需要保存到给定变量的值。在 Lua 中,这个函数可能会触发一个标签方法 ``setglobal‘‘ (参见 4.8 节)。为了设置全局变量的真正的值,而不唤起任何标签方法,在全局表上使用 lua_rawset (参见下面)。
所有的全局变量都保存在一个普通的表中。你可以获得这个表通过调用
void lua_getglobals (lua_State *L);
它把当前的全局变量的表压栈。为设置其它的表作为全局变量的表,调用
void lua_setglobals (lua_State *L);
将要使用的表出栈。
-------------------
5.10 操纵 Lua 中的表
-------------------
Lua 表也可以通过 API 来操作。
为读取表中的值,表必须在栈的某个地方。在这种情况下,调用
void lua_gettable (lua_State *L, int index);
这里 index 指定表。lua_gettable 出栈一个 key,并且返回(在栈上)表在 key 处的内容。在 Lua 中,这个操作可能会触发一个标签方法 ``gettable‘‘ 。为了获得表的 key 的真正值,而不唤起任何标签方法,使用 raw 版本的:
void lua_rawget (lua_State *L, int index);
为保存一个值到表中,表已经在栈的某个地方了,你压栈 key 和值(以这种顺序),然后调用
void lua_settable (lua_State *L, int index);
这里的 index 指定表。lua_settable 出栈 key 和 value。在 Lua 中,这个操作可能会触发一个标签方法 ``settable‘‘ 。为了设置任意表的 index 的真正值,而不唤起任何标签方法,使用 raw 版本的:
void lua_rawset (lua_State *L, int index);
最后,函数
void lua_newtable (lua_State *L);
创建一个新的空表并压栈。
-------------------
5.11 把表作为数组使用
-------------------
有 API 函数可以使用 Lua 表作为数组,也就是说,表的索引都有数值:
void lua_rawgeti (lua_State *L, int index, int n); void lua_rawseti (lua_State *L, int index, int n); int lua_getn (lua_State *L, int index);
lua_rawgeti 获得在栈的 index 处表的第 n 个元素。
lua_rawseti 设置在栈的 index 处表的第 n 个元素为栈顶的值。
lua_getn 返回在栈 index 处表的元素个数。这个数值是表中字段 n 的值,如果它有一个数值型值,或者表中最大的数值型索引不是一个 nil 值。
-------------------
5.12 调用 Lua 函数
-------------------
定义在 Lua 中的函数(或者注册到 Lua 的 C 函数)可以从宿主程序调用。这采用如下协议:首先,被调用的函数压栈;然后,函数的参数以直接顺序压栈(参见 5.5 节),即,第一个参数先压栈。最后,函数被调用通过:
int lua_call (lua_State *L, int nargs, int nresults);
这个函数返回和 lua_dostring (参见 5.8 节)同样的错误码。如果你想传播这个错误,而不是返回一个错误码,使用
void lua_rawcall (lua_State *L, int nargs, int nresults);
在这两个函数中,nargs 是你压栈的参数的个数。所有的参数和函数值都从栈中弹出,函数的返回值压栈。函数的返回值被调整(参见 4.3 节)为 nresults,除非 nresults 是 LUA_MULTRET。在这种情况下,所有的函数返回值都压栈。函数的返回值以直接顺序压栈(第一个返回值最先压栈),所以调用之后最后一个返回值在栈顶。
下面的例子显示宿主程序如何做到和 Lua 代码等价的:
a,b = f("how", t.x, 4)
在 C 中:
lua_getglobal(L, "t"); /* global `t‘ (for later use) */ lua_getglobal(L, "f"); /* function to be called */ lua_pushstring(L, "how"); /* 1st argument */ lua_pushstring(L, "x"); /* push the string `x‘ */ lua_gettable(L, -4); /* push result of t.x (2nd arg) */ lua_pushnumber(L, 4); /* 3rd argument */ lua_call(L, 3, 2); /* call function with 3 arguments and 2 results */ lua_setglobal(L, "b"); /* set global variable `b‘ */ lua_setglobal(L, "a"); /* set global variable `a‘ */ lua_pop(L, 1); /* remove `t‘ from the stack */
注意,上面的代码是平衡的:在它结束时,栈被回退为它的原来配置。这被认为是好的编译实践。
一些特别的 Lua 函数有它们自己的 C 接口。宿主程序可以生成一个 Lua 错误通过调用函数
void lua_error (lua_State *L, const char *message);
这个函数永远不返回。如果 lua_error 被从 Lua 调用的 C 函数中调用,相应的 Lua 执行被终结,就如同 Lua 代码中发生了一个错误。否则,整个宿主程序被一个 exit (EXIT_FAILURE)调用终止。在终结执行之前,message 被传递给错误处理函数 _ERRORMESSAGE (参见 4.7 节)。如果 message 为 NULL, _ERRORMESSAGE 便不会被调用。
标签方法可以通过下面的函数改变
void lua_settagmethod (lua_State *L, int tag, const char *event);
第二个参数是标签,第三个参数是事件名字(参见 4.8 节);新的方法从栈上弹出。为获得当前的
标签方法的值,使用函数
void lua_gettagmethod (lua_State *L, int tag, const char *event);
可以拷贝所有的标签方法从一个标签到另一个:
int lua_copytagmethods (lua_State *L, int tagto, int tagfrom);
这个函数返回 tagto。
你可以遍历一个表通过函数
int lua_next (lua_State *L, int index);
这里 index 指定被遍历的表。这个函数从栈中弹出一个 key,并从表中压栈一个 key-value 对(也就是给定 key 的下一个键值对)。如果没有元素了,函数返回 0 (不压栈)。一个典型的调用如下:
/* table is in the stack at index `t‘ */ lua_pushnil(L); /* first key */ while (lua_next(L, t) != 0) { /* `key‘ is at index -2 and `value‘ at index -1 */ printf("%s - %s\n", lua_typename(L, lua_type(L, -2)), lua_typename(L, lua_type(L, -1))); lua_pop(L, 1); /* removes `value‘; keeps `index‘ for next iteration */ }
函数
void lua_concat (lua_State *L, int n);
连接栈顶的 n 个值,弹出它们,并且压栈结果;n 最小为 2 。连接操作遵守 Lua 通用的语义(参见 4.5.5 节)。
-------------------
5.13 定义 C 函数
-------------------
注册 C 函数到 Lua ,用下面的宏:
#define lua_register(L, n, f) (lua_pushcfunction(L, f), lua_setglobal(L, n)) /* const char *n; */ /* lua_CFunction f; */
它接受函数在 Lua 中的名字,和一个函数指针。这个指针的类型必须为 lua_CFunction,其定义为:
typedef int (*lua_CFunction) (lua_State *L);
也就是一个 Lua 环境参数一个整型返回值的函数指针。
为了和 Lua 正确的交互,C 函数必须遵守下面的协议,这个协议规定了参数和返回值传递的方法:C 函数在栈里得到它从 Lua 获得的参数,以直接顺序(第一个参数最先压栈)。为了返回值到 Lua, C 函数可以把返回值顺序压栈(第一个返回值最先压栈),并且返回结果的个数。就像 Lua 函数一样,一个由 Lua 调用的 C 函数也可以返回多个值。
作为一个例子,下面的函数接受一个可变个数的数值型参数并且返回它们的平均数和总数:
static int foo (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ double sum = 0; int i; for (i = 1; i <= n; i++) { if (!lua_isnumber(L, i)) lua_error(L, "incorrect argument to function `average‘"); sum += lua_tonumber(L, i); } lua_pushnumber(L, sum/n); /* first result */ lua_pushnumber(L, sum); /* second result */ return 2; /* number of results */ }
函数可以作为 ‘average‘ 注册到 Lua ,通过调用
lua_register(L, "average", foo);
当新建一个 C 函数时,可以为它关联一些 upvalue (参见 4.6 节),从而新建一个 C 闭包,当函数被调用时这些值被传递给它,作为普通的参数。为了为了一个 C 函数关联 upvalue,首先这些值应该压栈。然后函数
void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);
被用来压栈 C 函数,参数 n 说明应该有多少个 upvalue 被关联到函数(这些 upvalue 从栈上弹出);事实上,宏 pushcfunction 被定义为参数 n 为 0 的 lua_pushcclosure 。然后,无论何时 C 函数被调用时,这些 upvalue 被插入函数作为最后的参数,在函数调用的实际参数之后。这使得程序容易获得 upvalue 而不必知道函数接受多少个参数(回忆一下,Lua 中的函数可以接受任意数量的参数):第 i 个 upvalue 在栈里的索引为 i-(n+1),这里 n 为 upvalue 的个数。
更多的 C 函数和闭包的例子,参见官方发布版中的文件 lbaselib.c, liolib.c, lmathlib.c, 和 lstrlib.c 。
-------------------
5.14 Lua 对象的引用
-------------------
如果一个 C 代码需要在一个 C 函数的生存期之外保持一个 Lua 值的话, 它必须新建那个值的引用(reference)。下面的函数可以管理这种引用:
int lua_ref (lua_State *L, int lock); int lua_getref (lua_State *L, int ref); void lua_unref (lua_State *L, int ref);
lua_ref 从栈顶弹出一个值,新建一个它的引用,并返回这个引用。一个 nil 值的引用永远为 LUA_REFNIL。(lua.h 也定义了一个不同于其它的有效引用的常量 LUA_NOREF 。)如果 lock 不是 0 的话,对象就被锁定:这意味着这个对象将不会被垃圾回收。没有锁定的引用可被垃圾回收。
当 C 中需要引用对象的时候,调用 lua_getref 把那个对象压栈;如果对象已经被垃圾回收了,lua_getref 返回 0 (不压栈任何东西)。
当一个引用不需要了,应该使用 lua_unref 调用来释放它。
注册
当 Lua 启动时,它在位置 LUA_REFREGISTRY 注意一个表。它可以通过宏来引用
#define lua_getregistry(L) lua_getref(L, LUA_REFREGISTRY)
这个表可以被 C 库作为一个注册机制使用。任何 C 库可以在这个表中保存数据,只要它使用的 key 不同于其它的库。
(未完待续)