lua与c之间交互详解(二)
本篇主要讲解下c如何调用Lua的,即c作为宿主语言,Lua为附加语言。c和Lua之间是通过Lua堆栈交互的,基本流程是:把元素入栈——从栈中弹出元素——处理——把结果入栈。关于Lua堆栈介绍以及Lua如何调用c参考其他两篇。
1. 加载运行Lua脚本
通过luaL_newstate()创建一个状态机L,c与Lua之间交互的api的第一个参数几乎都是L,是因为可以创建多个状态机,调用api需指定在哪个状态机上操作。lua_close(L)关闭状态机。
int main(int argc, char *argv[]){ lua_State *L = luaL_newstate(); //创建一个状态机 luaL_openlibs(L); //打开所有lua标准库 int ret = luaL_loadfile(L, "c2lua.lua"); //加载但不运行lua脚本 if(ret != LUA_OK){ const char *err = lua_tostring(L, -1); //加载失败,会把错误信息压入栈顶 printf("-------loadfile error = %s\n", err); lua_close(L); return 0; } ret = lua_pcall(L, 0, 0, 0); //保护模式调用栈顶函数 if(ret!=LUA_OK){ const char *err = lua_tostring(L, -1); //发生错误,会把唯一值(错误信息)压入栈顶 printf("-------pcall error = %s\n", err); lua_close(L); return 0; } lua_close(L); return 0; }
luaL_loadfile(L, filename):加载指定的Lua脚本,加载成功,把一个编译好的代码块作为Lua函数压入栈顶。若加载失败,会把错误信息压入栈顶
lua_pcall(L,0,0,0),在保护模式下调用栈顶函数,稍后介绍调用Lua函数时说明几个参数的作用,若运行发生错误,会把唯一值(错误信息)压入栈顶。luaL_openlibs(L)打开所有标准库,若不调用,则会因为找不到print函数而报错。
-- c2lua.lua print("------c2lua------")
在c中加载运行Lua脚本的流程通常是,luaL_newstate、luaL_openlibs、luaL_loadfile、lua_pcall
2. 操作Lua中全局变量
lua_getglobal(L, name),获取Lua脚本中命名为name的全局变量并压栈,然后c通过栈获取
void test_global(lua_State *L){ //读取,重置,设置全局变量 lua_getglobal(L, "var"); //获取全局变量var的值并压入栈顶 int var = lua_tonumber(L, -1); printf("var = %d\n", var); lua_pushnumber(L, 10); lua_setglobal(L, "var"); //设置全局变量var为栈顶元素的值,即10 lua_pushstring(L, "c str"); lua_setglobal(L, "var2"); //设置全局变量var2为栈顶元素的值,即c str lua_getglobal(L, "f"); lua_pcall(L,0,0,0); }
//c2lua.lua var = 5 function f() print("global var = ", var, var2) end
lua_setglobal(L, name),弹出栈顶的值,并设置为全局变量name的新值(name可以在Lua中未定义)
3. 调用Lua中函数
通过lua_pcall这个api在保护模式下调用一个Lua函数
int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);
nargs是函数参数的个数,nresults是函数返回值的个数。约定:调用前需要依次把函数,nargs个参数(从左向右)压栈(此时最后一个参数在栈顶位置),然后函数和所有参数都出栈,并调用指定的Lua函数。如果调用过程没有发生错误,会把nresults个结果(从左向右)依次压入栈中(此时最后一个结果在栈顶位置),并返回成功LUA_OK。如果发生错误,lua_pcall会捕获它,把唯一返回值(错误信息)压栈,然后返回特定的错误码。此时,如果设置msgh不为0,则会指定栈上索引msgh指向的位置为错误处理函数,然后以错误信息作为参数调用该错误处理函数,最后把返回值作为错误信息压栈。
void test_function(lua_State *L){ //调用lua函数 lua_getglobal(L, "f1"); lua_pcall(L, 0, 0, 0); //调用f1 lua_getglobal(L, "f2"); lua_pushnumber(L, 100); lua_pushnumber(L, 10); lua_pcall(L, 2, 2, 0); //调用f2 lua_getglobal(L, "f3"); char *str = "c"; lua_pushstring(L, str); lua_pcall(L,1,1,0); //调用f3 }
// c2lua.lua function f1() print("hello lua, I'm c!") end function f2(a, b) return a+b, a-b end function f3(str) return str .. "_lua" end
打印出栈的数据如下:栈顶是f3的返回值,接着是f2的2个返回值
4. 操作Lua中的table
对表的操作主要有查找t[k]、赋值t[k]=v以及遍历表。
//c2lua.lua t = {1, 2, ["a"] = 3, ["b"] = {["c"] = 'd'}}
int lua_getfield (lua_State *L, int index, const char *k);
查找,把t[k]的值压栈,t为栈上索引index指向的位置,跟Lua一样该api可能触发"index"事件对应的元方法,等价于lua_pushstring(L,const char*k)和lua_gettable(L, int index)两步,所以通常用lua_getfield在表中查找某个值。
void lua_setfield (lua_State *L, int index, const char *k);
赋值,等价于t[k]=v,将栈顶的值(v)出栈,其中t为栈上索引index指向的位置,跟Lua一样该api可能触发“newindex”事件对应的元方法。需先调用lua_pushxxx(L,v)将v入栈,再调用lua_setfield赋值。
void test_table(lua_State *L){ // 读取table lua_getglobal(L, "t"); lua_getfield(L, 1, "a"); //从索引为1的位置(table)获取t.a,并压栈 lua_getfield(L, 1, "b"); lua_getfield(L, -1, "c"); //从索引为-1的位置(栈顶)获取t.c,并压栈 // 修改table lua_settop(L, 1); //设置栈的位置为1,此时栈上只剩一个元素t lua_pushnumber(L, 10); lua_setfield(L, 1, "a"); //t.a=10 lua_pushstring(L, "hello c"); lua_setfield(L, 1, "e"); //t.e="hello c" dump_table(L, 1); //遍历table number-number 1-1 // number-number 1-2 // string-number a-3 // string-string e-hello c // string-table b-table //新建一个table lua_settop(L, 0); //清空栈 lua_newtable(L); //创建一个table lua_pushboolean(L, 0); lua_setfield(L, 1, "new_a"); lua_pushboolean(L, 1); lua_setfield(L, 1, "new_b"); dump_table(L, 1); //遍历table string-boolean new_a-false // string-boolean new_b-true }
注:lua_settop(L, int index)设置栈顶为index,大于index位置的元素都被移除,特别当index为0,即清空栈;如果原来的栈小于index,多余的位置用nil填充。
int lua_next (lua_State *L, int index);
通过lua_next遍历表t,t为索引index指向的位置,从栈顶弹出key,将该key的下一个key-value对压栈,此时key位于栈上-2位置,value位于-1位置。如果表中没有更多元素,则返回0。
void dump_table(lua_State *L, int index){ if(lua_type(L, index)!=LUA_TTABLE) return;
// 典型的遍历方法 lua_pushnil(L); //nil入栈,相当于从表的第一个位置遍历 while(lua_next(L, index)!=0){ //没有更多元素,lua_next返回0 //key-value入栈, key位于栈上-2处,value位于-1处 printf("%s-%s\n", lua_typename(L,lua_type(L,-2)), lua_typename(L,lua_type(L,-1))); lua_pop(L,1); //弹出一个元素,即把value出栈,此时栈顶为key,供下一次遍历 } }
总之,c调用lua的流程通常是:c把需要的数据入栈——Lua从栈中取出数据——执行Lua脚本——Lua把结果入栈——c从栈中获取结果