Lua教程(十七):C API简介
lua是一种嵌入式脚本语言,即lua不是可以单独运行的程序,在实际应用中,主要存在两种应用形式。第一种形式是,c/c++作为主程序,调用lua代码,此时可以将lua看做“可扩展的语言”,我们将这种应用称为“应用程序代码”。第二种形式是lua具有控制权,而c/c++代码则作为lua的“库代码”。在这两种形式中,都是通过lua提供的c api完成两种语言之间的通信的。
1. 基础知识:
c api是一组能使c/c++代码与lua交互的函数。其中包括读写lua全局变量、调用lua函数、运行一段lua代码,以及注册c函数以供lua代码调用等。这里先给出一个简单的示例代码:
#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>
int main(void)
{
const char* buff = "print(\"hello\")";
int error;
lua_state* l = lual_newstate();
lual_openlibs(l);
error = lual_loadbuffer(l,buff,strlen(buff),"line") || lua_pcall(l,0,0,0);
int s = lua_gettop(l);
if (error) {
fprintf(stderr,"%s",lua_tostring(l,-1));
lua_pop(l,1);
}
lua_close(l);
return 0;
}
下面是针对以上代码给出的具体解释:
1). 上面的代码是基于我的c++工程,而非c工程,因此包含的头文件是lua.hpp,如果是c工程,可以直接包含lua.h。
2). lua库中没有定义任何全局变量,而是将所有的状态都保存在动态结构lua_state中,后面所有的c api都需要该指针作为第一个参数。
3). lual_openlibs函数是用于打开lua中的所有标准库,如io库、string库等。
4). lual_loadbuffer编译了buff中的lua代码,如果没有错误,则返回0,同时将编译后的程序块压入虚拟栈中。
5). lua_pcall函数会将程序块从栈中弹出,并在保护模式下运行该程序块。执行成功返回0,否则将错误信息压入栈中。
6). lua_tostring函数中的-1,表示栈顶的索引值,栈底的索引值为1,以此类推。该函数将返回栈顶的错误信息,但是不会将其从栈中弹出。
7). lua_pop是一个宏,用于从虚拟栈中弹出指定数量的元素,这里的1表示仅弹出栈顶的元素。
8). lua_close用于释放状态指针所引用的资源。
2. 栈:
在lua和c语言之间进行数据交换时,由于两种语言之间有着较大的差异,比如lua是动态类型,c语言是静态类型,lua是自动内存管理,而c语言则是手动内存管理。为了解决这些问题,lua的设计者使用了虚拟栈作为二者之间数据交互的介质。在c/c++程序中,如果要获取lua的值,只需调用lua的c api函数,lua就会将指定的值压入栈中。要将一个值传给lua时,需要先将该值压入栈,然后调用lua的c api,lua就会获取该值并将其从栈中弹出。为了可以将不同类型的值压入栈,以及从栈中取出不同类型的值,lua为每种类型均设定了一个特定函数。
1). 压入元素:
lua针对每种c类型,都有一个c api函数与之对应,如:
void lua_pushnil(lua_state* l); --nil值
void lua_pushboolean(lua_state* l, int b); --布尔值
void lua_pushnumber(lua_state* l, lua_number n); --浮点数
void lua_pushinteger(lua_state* l, lua_integer n); --整型
void lua_pushlstring(lua_state* l, const char* s, size_t len); --指定长度的内存数据
void lua_pushstring(lua_state* l, const char* s); --以零结尾的字符串,其长度可由strlen得出。
对于字符串数据,lua不会持有他们的指针,而是调用在api时生成一个内部副本,因此,即使在这些函数返回后立刻释放或修改这些字符串指针,也不会有任何问题。
在向栈中压入数据时,可以通过调用下面的函数判断是否有足够的栈空间可用,一般而言,lua会预留20个槽位,对于普通应用来说已经足够了,除非是遇到有很多参数的函数。
int lua_checkstack(lua_state* l, int extra) --期望得到extra数量的空闲槽位,如果不能扩展并获得,返回false。
2). 查询元素:
api使用“索引”来引用栈中的元素,第一个压入栈的为1,第二个为2,依此类推。我们也可以使用负数作为索引值,其中-1表示为栈顶元素,-2为栈顶下面的元素,同样依此类推。
lua提供了一组特定的函数用于检查返回元素的类型,如:
int lua_isboolean (lua_state *l, int index);
int lua_iscfunction (lua_state *l, int index);
int lua_isfunction (lua_state *l, int index);
int lua_isnil (lua_state *l, int index);
int lua_islightuserdata (lua_state *l, int index);
int lua_isnumber (lua_state *l, int index);
int lua_isstring (lua_state *l, int index);
int lua_istable (lua_state *l, int index);
int lua_isuserdata (lua_state *l, int index);
以上函数,成功返回1,否则返回0。需要特别指出的是,对于lua_isnumber而言,不会检查值是否为数字类型,而是检查值是否能转换为数字类型。
lua还提供了一个函数lua_type,用于获取元素的类型,函数原型如下:
int lua_type (lua_state *l, int index);
该函数的返回值为一组常量值,分别是:lua_tnil、lua_tnumber、lua_tboolean、lua_tstring、lua_ttable、lua_tfunction、lua_tuserdata、lua_tthread和lua_tlightuserdata。这些常量通常用于switch语句中。
除了上述函数之外,lua还提供了一组转换函数,如:
int lua_toboolean (lua_state *l, int index);
lua_cfunction lua_tocfunction (lua_state *l, int index);
lua_integer lua_tointeger (lua_state *l, int index);
const char *lua_tolstring (lua_state *l, int index, size_t *len);
lua_number lua_tonumber (lua_state *l, int index);
const void *lua_topointer (lua_state *l, int index);
const char *lua_tostring (lua_state *l, int index);
void *lua_touserdata (lua_state *l, int index);
--string类型返回字符串长度,table类型返回操作符'#'等同的结果,userdata类型返回分配的内存块长度。
size_t lua_objlen (lua_state *l, int index);
对于上述函数,如果调用失败,lua_toboolean、lua_tonumber、lua_tointeger和lua_objlen均返回0,而其他函数则返回null。在很多时候0不是一个很有效的用于判断错误的值,但是ansi c没有提供其他可以表示错误的值。因此对于这些函数,在有些情况下需要先使用lua_is*系列函数判断是否类型正确,而对于剩下的函数,则可以直接通过判断返回值是否为null即可。
对于lua_tolstring函数返回的指向内部字符串的指针,在该索引指向的元素被弹出之后,将无法保证仍然有效。该函数返回的字符串末尾均会有一个尾部0。
下面将给出一个工具函数,可用于演示上面提到的部分函数,如:
static void stackdump(lua_state* l)
{
int top = lua_gettop(l);
for (int i = 1; i <= top; ++i) {
int t = lua_type(l,i);
switch(t) {
case lua_tstring:
printf("'%s'",lua_tostring(l,i));
break;
case lua_tboolean:
printf(lua_toboolean(l,i) ? "true" : "false");
break;
case lua_tnumber:
printf("%g",lua_tonumber(l,i));
break;
default:
printf("%s",lua_typename(l,t));
break;
}
printf("");
}
printf("\n");
}
3). 其它栈操作函数:
除了上面给出的数据交换函数之外,lua的c api还提供了一组用于操作虚拟栈的普通函数,如:
int lua_gettop(lua_state* l); --返回栈中元素的个数。
void lua_settop(lua_state* l, int index); --将栈顶设置为指定的索引值。
void lua_pushvalue(lua_state* l, int index); --将指定索引的元素副本压入栈。
void lua_remove(lua_state* l, int index); --删除指定索引上的元素,其上面的元素自动下移。
void lua_insert(lua_state* l, int index); --将栈顶元素插入到该索引值指向的位置。
void lua_replace(lua_state* l, int index); --弹出栈顶元素,并将该值设置到指定索引上。
lua还提供了一个宏用于弹出指定数量的元素:
见如下示例代码:
int main()
{
lua_state* l = lual_newstate();
lua_pushboolean(l,1);
lua_pushnumber(l,10);
lua_pushnil(l);
lua_pushstring(l,"hello");
stackdump(l); //true 10 nil 'hello'
lua_pushvalue(l,-4);
stackdump(l); //true 10 nil 'hello' true
lua_replace(l,3);
stackdump(l); //true 10 true 'hello'
lua_settop(l,6);
stackdump(l); //true 10 true 'hello' nil nil
lua_remove(l,-3);
stackdump(l); //true 10 true nil nil
lua_settop(l,-5);
stackdump(l); //true
lua_close(l);
return 0;
}
3. c api中的错误处理:
1). c程序调用lua代码的错误处理:
通常情况下,应用程序代码是以“无保护”模式运行的。因此,当lua发现“内存不足”这类错误时,只能通过调用“紧急”函数来通知c语言程序,之后在结束应用程序。用户可通过lua_atpanic来设置自己的“紧急”函数。如果希望应用程序代码在发生lua错误时不会退出,可通过调用lua_pcall函数以保护模式运行lua代码。这样再发生内存错误时,lua_pcall会返回一个错误代码,并将解释器重置为一致的状态。如果要保护与lua的c代码,可以使用lua_cpall函数,它将接受一个c函数作为参数,然后调用这个c函数。
2). lua调用c程序:
通常而言,当一个被lua调用的c函数检测到错误时,它就应该调用lua_error,该函数会清理lua中所有需要清理的资源,然后跳转回发起执行的那个lua_pcall,并附上一条错误信息。
上一篇: Linux 中的Edquota命令
下一篇: Lua教程(五):迭代器和泛型for