C++调用Lua API接口

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_callnargs 是你压入堆栈的参数个数。当函数调用完毕后,所有的参数以及函数本身都会出栈。而函数的返回值这时则被压入堆栈。返回值的个数将被调整为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


lua_dump

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值