1 CAPI简介
Lua与C可以有两种方式进行交互,一种是把LUA的功能作为库进行使用。另一种是在LUA中调用C库的功能,二者都可以通过CPAI的方式在LUA与C之间建立起桥梁。主要的数据结构是一个虚拟栈,大部分API均会操作栈上的值,进行数据交换。栈由Lua管理,垃圾收集器能对无用数据进行回收。
CAPI的能力包括读写LUA全局变量、调用LUA函数、运行LUA代码,以及注册C函数以供LUA代码调用等。
通过lua.h头文件可以查看这些函数原型。
另一个头文件是lauxlib.h,其中声明函数原型为luaL_*,其使用CAPI,提供了高层抽象调用LUA的功能。
1.1 栈的相关操作
栈可以保存任何类型的Lua值,每种LUA类型都有一个API以压栈对应的数据。
默认栈中有20个空糟,可以用来存放数据,需要有必要可以检查栈的空间是否满足需要
int lua_checkstack(lua_State* L,int sz)
1.2 CAPI的错误处理
由于C没有异常处理的相关机制,因此如果在LUA中出现异常,需要在C中进行处理。
一种方式是使用lua_atpanic一注册一个异常处理函数,以通知C程序发生了错误。
另一种方式是使用lua_pcall来来运行LUA代码。其会处理LUA中发生的异常并返回一个错误。p是protect的缩写,意为保护模式下的调用。
如果在LUA中调用C代码,则可以使用lua_cpcall,它将处理C中类似内存分配失败这类严重错误。在5.2中此函数已被删除,可以使用lua_pushfunction压入函数,然后调用lua_pcall来实现。
如果是在实现LUA的C模块代码,发生了错误,则需要调用lua_error,以返回给lua_pcall错误信息。
下面的代码模拟了一个最简单的LUA解析器,从缓冲区中读取一个字符串并放到LUA中执行。
2.2 传递表结构数据的API
2.3 调用Lua函数
把要调用的函数名压入栈顶,然后把参数依次压入栈顶,然后调用lua_pcall,最后从栈中弹出调用结果。
int lua_pcall(lua_State* L,int nargs,int nrets,int msgh); 在Lua中执行L中的调用,参数分别是传入参数个数、返回参数个数。
如果是普通错误,则返回LUA_ERRRUN; 如果是内存错误则返回LUA_ERRMEM,如果是运行错误处理函数,则返回LUA_ERRERR。
3 LUA中调用C
3.1 调用C函数
LUA调用C函数也需要遵循一定的协议,C函数原原型必须是int func(lua_State* L);
传递参数也使用栈,C函数从栈中获取传递进来的参数;函数的返回值表示结果的个数,结果需要在函数中压入到栈中。
实现函数以后,还必须能让LUA环境启动后能找到新定义的C函数。对于简单的函数,可以在linit.c中的luaL_openlibs()中函数压入栈,并为之设置全局函数名。
3.2 使用C模块
只使用单个C函数基本上没有什么意义,最常用的是实现一个比较复杂的模块,然后让Lua支持之。其模式为:
Lua与C可以有两种方式进行交互,一种是把LUA的功能作为库进行使用。另一种是在LUA中调用C库的功能,二者都可以通过CPAI的方式在LUA与C之间建立起桥梁。主要的数据结构是一个虚拟栈,大部分API均会操作栈上的值,进行数据交换。栈由Lua管理,垃圾收集器能对无用数据进行回收。
CAPI的能力包括读写LUA全局变量、调用LUA函数、运行LUA代码,以及注册C函数以供LUA代码调用等。
通过lua.h头文件可以查看这些函数原型。
另一个头文件是lauxlib.h,其中声明函数原型为luaL_*,其使用CAPI,提供了高层抽象调用LUA的功能。
1.1 栈的相关操作
栈可以保存任何类型的Lua值,每种LUA类型都有一个API以压栈对应的数据。
void lua_pushnil(lua_State *L);
void lua_pushboolean(lua_State *L,int bool)
void lua_pushnumber(lua_State *L,lua_Number n)
void lua_pushinteger(lua_State *L,lua_Integer n)
void lua_pushstring(lua_State *L,const char* s);
void lua_pushlstring(lua_State *L,const char* s,size_t len);
默认栈中有20个空糟,可以用来存放数据,需要有必要可以检查栈的空间是否满足需要
int lua_checkstack(lua_State* L,int sz)
栈中的数据底从1开始,但从顶部访问时,则用负的索引,-1表示顶部元素,-2表示其下一个元素,依次推
每种类型都有一个函数用来从栈中取出其值
lua_tostring(lua_State *L, int idx)
lua_tolstring(lua_State *L, int idx,size_t* len) //从栈中获取字符串,长度在于len中。
lua_tonumber(lua_State *L, int idx)
lua_tointeger(lua_State *L, int idx)
lua_objlen(lua_State *L, int idx) //可以获取表长度、userdata的大小
每种类型都有相应的类型判断函数
lua_isnumber(lua_State *L, int idx)
lua_isstring(lua_State *L, int idx)
lua_istable(lua_State *L, int idx)
有一个总的类型分析函数
int lua_type(lua_State *L, int idx) //返回类型,包含LUA_TNIL,TBOOLEAN,TNUMBER,TSTRING,TTABLE,TTHREAD,TUSERDATA,TFUNCTION。
char* lua_typename(lua_State*L,int type)
栈的部分操作
int lua_gettop(lua_State *L) 获取栈中元素个数
void lua_settop(lua_State *L, int idx) 重置栈顶指针指向idx的位置
void lua_pushvalue(lua_State *L, int idx) 复制idx位置元素并压入到栈顶
void lua_remove(lua_State *L, int idx) 移除指定idx位置的元素,其上元素依次下移
void lua_insert(lua_State *L, int idx) 把栈顶元素插入到idx位置
void lua_replace(lua_State *L, int idx) 把栈顶元素复制到idx位置,并弹出栈顶元素
1.2 CAPI的错误处理
由于C没有异常处理的相关机制,因此如果在LUA中出现异常,需要在C中进行处理。
一种方式是使用lua_atpanic一注册一个异常处理函数,以通知C程序发生了错误。
另一种方式是使用lua_pcall来来运行LUA代码。其会处理LUA中发生的异常并返回一个错误。p是protect的缩写,意为保护模式下的调用。
如果在LUA中调用C代码,则可以使用lua_cpcall,它将处理C中类似内存分配失败这类严重错误。在5.2中此函数已被删除,可以使用lua_pushfunction压入函数,然后调用lua_pcall来实现。
如果是在实现LUA的C模块代码,发生了错误,则需要调用lua_error,以返回给lua_pcall错误信息。
2 C中调用LUA
直接在C中调用LUA相当于实现一个嵌入式的LUA解析器。可以调用LUA的标准函数、或利用其标准库的功能。与LUA运行时交互的输入及输出数据,通过lua_State中的栈来实现。CAPI提供了众多接口以方便的在C中操作栈,以及向栈中放入C数据或LUA运行时数据的方法。
下面的代码模拟了一个最简单的LUA解析器,从缓冲区中读取一个字符串并放到LUA中执行。
lua_State *L = luaL_newstate();
luaL_openlibs(L);
if( luaL_loadbuffer(L,buff,strlen(buff),"line") ||
lua_pcall(L,0,0,0))
{
//handle error
lua_pop(L,1);
}
lua_close(L);
2.2 传递表结构数据的API
int luaL_loadfile(lua_State*L,char* file,int mode) 在Lua中加载文件
void lua_getglobal(lua_State* L,char* name) 将Lua全局环境中名为name的变量压入栈顶
void lua_setglobal(lua_State* L,char* name) 弹栈,把名为name的Lua全局变量的值设置为此值
void lua_getfield(lua_State* L,int idx,char* key) 将idx位置的table的key对应的值压入栈顶
void lua_setfield(lua_State* L,int idx,char* key) 将idx位置的table的key对应的值设置为栈顶元素的值
void lua_gettable(lua_State* L,int idx) 当前栈顶为key,把idx位置的table的key对应的值压入栈顶
void lua_settable(lua_State* L,int idx) 当前栈顶为value,栈顶下为key,把idx位置的table的key的值置为value
void lua_rawget(lua_State* L,int idx) 类lua_gettable,效率高
void lua_rawset(lua_State* L,int idx) 类lua_settable,效率高
void lua_newtable(lua_State* L) 创建一个新表并压入栈顶
2.3 调用Lua函数
把要调用的函数名压入栈顶,然后把参数依次压入栈顶,然后调用lua_pcall,最后从栈中弹出调用结果。
int lua_pcall(lua_State* L,int nargs,int nrets,int msgh); 在Lua中执行L中的调用,参数分别是传入参数个数、返回参数个数。
如果是普通错误,则返回LUA_ERRRUN; 如果是内存错误则返回LUA_ERRMEM,如果是运行错误处理函数,则返回LUA_ERRERR。
3 LUA中调用C
3.1 调用C函数
LUA调用C函数也需要遵循一定的协议,C函数原原型必须是int func(lua_State* L);
传递参数也使用栈,C函数从栈中获取传递进来的参数;函数的返回值表示结果的个数,结果需要在函数中压入到栈中。
static int wrap_sin(lua_State* L)
{
double d = lua_tonumber(L,1);
double r = sin(d);
lua_pushnumber(L,r);
return 1;
}
实现函数以后,还必须能让LUA环境启动后能找到新定义的C函数。对于简单的函数,可以在linit.c中的luaL_openlibs()中函数压入栈,并为之设置全局函数名。
lua_pushfunction(L,wrap_sin);
lua_setglobal(L,"wsin");
3.2 使用C模块
只使用单个C函数基本上没有什么意义,最常用的是实现一个比较复杂的模块,然后让Lua支持之。其模式为:
//模块导出函数实现略,假定实现了func1,func2等一批导出函数
static const struct luaL_Reg exports[] = {
{"fun1",func1},
{"fun2",func2},
...
{NULL,NULL}
}
int luaopen_libname(lua_State* L)
{
luaL_register(L,"libname",exports);
return 1;
}
luaL_register将为模块生成一个同名的table,并用exports的信息填充这个表,然后把这个表放在栈L中。返回后表将带入到lua环境中。
lua源代码的lmathlib.c是一个很好的学习示例,它简单的封装了C的math库。
如果解释器支持动态链接,则上面这套机制可以正常工作。如果不支持动态链接,那么必须用新的模块重新编译Lua。最简单的方法是在linit.c中,把luaopen_libname的调用加到luaL_openlibs函数中。