用C语言操作lua
lua提供了一套C语言的API供用户使用C语言与lua进行交互, 本文通过解读lua C Application Program Interface并且提供具体的实例来表明如何使用C操作lua, 本文以lua5.1为例, lua5.3支持了一部分新的特性不过都大同小异
Lua Stack
对于C, Lua之间的交互, 很自然的会想到如何获得lua的数据,如何将处理的结果返回给lua, lua使用一个Virtual Stack将其函数调用的参数传递给C 如下所示 加入我们有一个lua的调用 fun(a, b, c) 这个调用会将 a, b, c三个参数依次压栈, 在Virtual Stack中形成这样的结构:
| c | |-------| | b | |-------| | a | +-------+
为了方便用户取用函数的任意参数, lua的 Virtual Stack不仅允许你访问栈顶元素, 同时允许访问栈内任何位置的元素, lua的API均有一个参数为index, 用于指定对栈上的哪一个参数进行操作, 最后, C语言处理之后的结果要返回给lua, 也是通过压栈的形式, 将结果压栈, 因为lua支持多个返回值, C语言通过返回一个int, 表明将自栈顶算起的多少参数作为调用该lua函数的返回值, 其他的参数均被丢弃掉. 下面通过几个小的demo来说明一下如何通过C语言对lua进行操作. 我们使用lua_sandbox来演示如何用C语言实现lua的函数.
获取用户的每一个传递的参数
首先介绍一下如何实现一个C语言函数并且可以导出到lua_sandbox中, 我们需要做两个工作, 第一个是实现这个函数, 函数的实现标准如下:
int my_function_name(lua_State* lua)
然后使用
lsb_add_function(lsb, &your_fun_name, "the_name_to_export")
将该函数添加到你要运行的lua_sandbox实例中, 即可在lua代码内调用你写的函数啦
下面我们实现这个函数:
int get_args(lua_State* lua) { int n = lua_gettop(lua); for(int i = 1; i <= n; i++) { double arg = lua_tonumber(lua, i); printf("%lf\n", arg); } return 0; }
然后我们将函数export为 get_args 在lua中调用该函数
get_args(1, 2, 3, 4, 5)
就会依次输出这几个参数, 下面我们进行一个稍微复杂点的例子, 遍历输出lua的table
输出lua的table
C语言中是没有map, 这种结构的, 即key-value存储结构, C++通过STL对map实现了支持, 而lua则原生支持map, lua支持一种叫做table的数据结构, 为key-value对, 而且一个table内可以存储多种不同类型的数据, 为了让C语言也能访问lua的table结构,lua API提供了 lua_next 这个函数
lua_next首先将Virtual Stack中的一个参数pop出栈, 然后将index所指定的位置的表格内的下一个元素的key, value依次压栈, 即value在栈顶, key 在次栈顶, 将整个表格遍历完毕之后, lua_next将会返回 NULL, 此时终止遍历即可 ,下面是遍历实现的代码
int visit_table(lua_State* lua) { lua_debug_table(lua, lua_gettop(lua)); return 0; } static int lua_debug_table(lua_State *lua, int index) { //It's not a table if(lua_type(lua, index)!=LUA_TTABLE) { return 0; } lua_pushnil(lua); while(lua_next(lua, index) != 0) { if(lua_type(lua, -1) == LUA_TTABLE) { char *keyname = lua_tolstring(lua, -2, NULL); for(int i = 0; i < padding; i++) fprintf(stderr, " "); fprintf(stderr,"%s[%s] - %s\n", keyname, lua_typename(lua, lua_type(lua, -2)) ,lua_typename(lua, lua_type(lua, -1))); padding+=2; lua_debug_table(lsb, lua_gettop(lua)); lua_pop(lua, 1); padding-=2; } else { char *keyname = lua_tolstring(lua, -2, NULL); for(int i = 0; i < padding; i++) fprintf(stderr, " "); fprintf(stderr,"%s[%s] - %s\n", keyname, lua_typename(lua, lua_type(lua, -2)) ,lua_typename(lua, lua_type(lua, -1))); lua_pop(lua, 1); } } return 0; }
* 一个警告: 在一个迭代中,小心使用index=-1指定lua栈顶元素, 除非你确定你的确是要指向栈顶元素,而不是要指向当前的栈顶元素, 这么说可能有点绕,我举个例子来说明
我们看一个精简版的visit_table的例子
/* table is in the stack at index 't' */ lua_pushnil(L); /* first key */ while (lua_next(L, t) != 0) { /* uses 'key' (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))); /* removes 'value'; keeps 'key' for next iteration */ lua_pop(L, 1); }
如果这个table就在栈顶的话, 我们可不可以将t换成 -1呢?指向 栈顶的table? 答案是不可以, 如果换成了-1 那么 lua_next第一次执行之后因为取出了key-value对,目前栈顶的元素发生了改变,变成了这个table内此次迭代的, 而 index = -1还会去取栈顶的元素, 这时候lua_next访问的table就不是你想要访问的那个table了, 而是这个table内的某个value, lua_next就很可能无法继续下去了(说很可能无法继续下去的原因,是因为如果这个value是一个table的话, 那么lua_next还可以继续下去,只不过不可能得到正确的结果), 那么这种情况下如果想指定当前栈顶的元素为lua_next操作的table的话,应该这样指定:
int index = lua_gettop(lua);