用C语言操作lua

用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);

 

Leave a Reply

Your email address will not be published. Required fields are marked *

thirteen + 17 =

This site uses Akismet to reduce spam. Learn how your comment data is processed.