lua栈理解及lua和C++的数据交换API介绍 .(转)

lua闭包的理解:

http://www.cnblogs.com/ringofthec/archive/2010/11/05/luaClosure.html

1.理解lua的栈到底是什么?

lua的栈类似于以下的定义,它是在创建lua_State的时候创建的:

[cpp]viewplaincopyprint?

TValuestack[max_stack_len]//欲知内情可以查lstate.c的stack_init函数

TValuestack[max_stack_len]//欲知内情可以查lstate.c的stack_init函数

存入栈的数据类型包括数值,字符串,指针,talbe,闭包等,下面是一个栈的例子:

lua栈

执行下面的代码就可以让你的lua栈上呈现图中的情况

[cpp]viewplaincopyprint?

lua_pushcclosure(L,func,0)//创建并压入一个闭包

lua_createtable(L,0,0)//新建并压入一个表

lua_pushnumber(L,343)//压入一个数字

lua_pushstring(L,“mystr”)//压入一个字符串

lua_pushcclosure(L,func,0)//创建并压入一个闭包lua_createtable(L,0,0)//新建并压入一个表lua_pushnumber(L,343)//压入一个数字lua_pushstring(L,“mystr”)//压入一个字符串

[cpp]viewplaincopyprint?

堆栈的序号可以从栈顶和栈底计数,从栈底计数,则栈底是1,向栈顶方向递增。从栈顶计数,则栈顶是-1,向栈底方向递减。一般都用从栈顶计数的方式。堆栈的默认大小是20,可以用lua_checkstack修改.用lua_gettop则可以获得栈里的元素数目。并不是说在栈顶有一个整形元素。而是计算了一下栈顶元素在栈里的正index,相当于元素数目。

堆栈的序号可以从栈顶和栈底计数,从栈底计数,则栈底是1,向栈顶方向递增。从栈顶计数,则栈顶是-1,向栈底方向递减。一般都用从栈顶计数的方式。堆栈的默认大小是20,可以用lua_checkstack修改.用lua_gettop则可以获得栈里的元素数目。并不是说在栈顶有一个整形元素。而是计算了一下栈顶元素在栈里的正index,相当于元素数目。这里要说明的是,你压入的类型有数值,字符串,表和闭包[在c中看来是不同类型的值],但是最后都是统一用TValue这种数据结构来保存的:),下面用图简单的说明一下这种数据结构:

lua

TValue结构对应于lua中的所有数据类型,是一个{值,类型}结构,这就lua中动态类型的实现,它把值和类型绑在一起,用tt记录value的类型,value是一个联合结构,由Value定义,可以看到这个联合有四个域,先说明简单的

p--可以存一个指针,实际上是lua中的lightuserdata结构

n--所有的数值存在这里,不过是int,还是float

b--Boolean值存在这里,注意,lua_pushinteger不是存在这里,而是存在n中,b只存布尔

gc--其他诸如table,thread,closure,string需要内存管理垃圾回收的类型都存在这里

gc是一个指针,它可以指向的类型由联合体GCObject定义,从图中可以看出,有string,userdata,closure,table,proto,upvalue,thread

从下面的图可以的得出如下结论:

1.lua中,number,boolean,nil,lightuserdata四种类型的值是直接存在栈上元素里的,和垃圾回收无关.

2.lua中,string,table,closure,userdata,thread存在栈上元素里的只是指针,他们都会在生命周期结束后被垃圾回收.

2.lua和c通信的约定

lua和c通信时有这样的约定:所有的lua中的值由lua来管理,c++中产生的值lua不知道,类似表达了这样一种意思:"如果你(c/c++)想要什么,你告诉我(lua),我来产生,然后放到栈上,你只能通过api来操作这个值,我只管我的世界",这个很重要,因为:

"如果你想要什么,你告诉我,我来产生"就可以保证,凡是lua中的变量,lua要负责这些变量的生命周期和垃圾回收,所以,必须由lua来创建这些值(在创建时就加入了生命周期管理要用到的簿记信息)

"然后放到栈上,你只能通过api来操作这个值",luaapi给c提供了一套完备的操作界面,这个就相当于约定的通信协议,如果lua客户使用这个操作界面,那么lua本身不会出现任何"意料之外"的错误.

"我只管我的世界"这句话体现了lua和c/c++作为两个不同系统的分界,c/c++中的值,lua是不知道的,lua只负责它的世界

3.luavalue和cvalue的对应关系

clua

nil无{value=0,tt=t_nil}

booleanint非0,0{value=非0/0,tt=t_boolean}

numberint/float等1.5{value=1.5,tt=t_number}

lightuserdatavoid*,int*,各种*point{value=point,tt=t_lightuserdata}

stringcharstr[]{value=gco,tt=t_string}gco=TStringobj

table无{value=gco,tt=t_table}gco=Tableobj

userdata无{value=gco,tt=t_udata}gco=Udataobj

closure无{value=gco,tt=t_function}gco=Closureobj

可以看出来,lua中提供的一些类型和c中是对应的,也提供一些c中没有的类型.其中有一些药特别的说明一下:

nil值,c中没有对应,但是可以通过lua_pushnil向lua中压入一个nil值

注意:lua_push*族函数都有"创建一个类型的值并压入"的语义,因为lua中所有的变量都是lua中创建并保存的,对于那些和c中有对应关系的lua类型,lua会通过api传来的附加参数,创建出对应类型的lua变量放在栈顶,对于c中没有对应类型的lua类型,lua直接创建出对应变量放在栈顶.

例如:lua_pushstring(L,“string”)lua根据"string"创建一个TStringobj,绑定到新分配的栈顶元素上

lua_pushcclosure(L,func,0)lua根据func创建一个Closureobj,绑定到新分配的栈顶元素上

lua_pushnumber(L,5)lua直接修改新分配的栈顶元素,将5赋值到对应的域

lua_createtable(L,0,0)lua创建一个Tabkeobj,绑定到新分配的栈顶元素上

总之,这是一个cvalue–>luavalue的流向,不管是想把一个简单的c数据放入lua的世界,还是创建一个table,都会导致

1.栈顶新分配元素2.绑定或赋值

还是为了重复一句话,一个cvalue入栈就是进入了lua的世界,lua会生成一个对应的结构并管理起来,从此就不再依赖这个cvalue

luavalue–>cvalue时,是通过lua_to*族api实现,很简单,取出对应的c中的域的值就行了,只能转化那些c中有对应值的luavalue,比如table就不能tocvalue,所以api中夜没有提供lua_totable这样的接口.

本资料整理自网络!

--------------------------------------------------------------------------------------------------------

这些东西是平时遇到的,觉得有一定的价值,所以记录下来,以后遇到类似的问题可以查阅,同时分享出来也能方便需要的人,转载请注明来自RingOfTheC[ring.of.the.c@gmail.com]

1.建一个新表

voidlua_createtable(lua_State*L,intnarr,intnrec)创建一个新的table,并把它放在栈顶.narr和nrec分别指定该table的array部分和hash部分的预分配元素数量无返回值栈高度+1,栈顶元素是新table#definelua_newtable(L)lua_createtable(L,0,0)常用这个2.取表中的元素voidlua_getfield(lua_State*L,intindex,constchar*k)操作:arr=Stack[index]//arr肯定是表Stack.push(arr[k])取表中键为k的元素,这里的表是由index指向的栈上的一个表无返回值栈高度+1,栈顶元素是(Stack[index])[k]注意,该操作将触发__index元方法3.给表中的元素赋值voidlua_setfield(lua_State*L,intindex,constchar*k)操作:arr=Stack[index]arr[k]=Stack.top()Stack.pop()给表中键为k的元素赋值value(value就是栈顶元素),这里的表是由index指向的栈上的一个表无返回值栈高度-1,被弹出的是value注意,该操作将触发__newindex元方法4.取表元素和表元素赋值voidlua_gettable(lua_State*L,intindex)操作:ele=Stack[index]

key=Stack.top()

Stack.pop()

value=ele[key]

Stack.push(value)

根据index指定取到相应的表;取栈顶元素为key,并弹出栈;获取表中key的值压入栈顶.

无返回值

栈高度不变,但是发生了一次弹出和压入的操作,弹出的是key,压入的是value

注意,该操作将触发__index元方法

voidlua_settable(lua_State*L,intindex)操作:ele=Stack[index]value=Stack.top()Stack.pop()key=Stack.top()Stack.pop()ele[key]=value根据index指定取到相应的表;取栈顶元素做value,弹出之;再取当前栈顶元素做key,亦弹出之;然后将表的键为key的元素赋值为value无返回值栈高度-2,第一次弹出value,第二次弹出key注意,该操作将触发__newindex元方法5.对table的一些操作[不引发原方法]voidlua_rawget(lua_State*L,intindex)和lua_gettable操作一样

但是不触发相应的元方法

voidlua_rawgeti(lua_State*L,intindex,intn)

操作:ele=Stack[index]

value=ele[n]

Stack.push(value)

无返回值

栈+1,栈顶新增元素就是value

不触发相应的元方法

voidlua_rawset(lua_State*L,intindex)和lua_settable操作一样

但是不触发相应的原方法

voidlua_rawseti(lua_State*L,intindex,intn)操作:ele=Stack[index]

value=Stack.top()

Stack.pop()

ele[n]=value

无返回值

栈-1,栈顶将value弹出

不触发相应的元方法

6.复制栈上元素并压入栈

voidlua_pushvalue(lua_State*L,intindex)操作:value=Stack[index]

Stack.push(value)

无返回值

栈+1

7.创建一个元表

intluaL_newmetatable(lua_State*L,constchar*tname)操作:1.在注册表中查找tname,如果已经注册,就返回0,否者继续,并平栈

lua_getfield(L,LUA_REGISTRYINDEX,tname)

if(!lua_isnil(L,-1))

return0;

lua_pop(L,1);

2.创建一个表,并注册,返回1

lua_newtable(L)

lua_pushvalue(L,-1)

lua_setfield(L,LUA_REGISTRYINDEX,tname)

return1

有返回值栈+1,栈顶元素是在注册表中注册过的新表8.创建C值void*lua_newuserdata(lua_State*L,size_tsize)该函数分配一块由size指定大小的内存块,并放在栈顶

返回值是新分配的块的地址

栈+1,栈顶是userdata

userdata用来在lua中表示c中的值.一个完整的userdata有自己的元表,在垃圾回收时,可以调用它的元表的__gc方法

9.注册c函数到lua中,其实没有这回事,lua中只有c闭包

voidlua_pushcclosure(lua_State*L,lua_CFunctionfn,intn)向栈上压一个C闭包

当一个c函数被创建时,可以绑定几个值在它上面,从而形成一个闭包.在任何时刻调用这个c函数时,都可以访问这几个绑定值.

绑定的方法:先一次压入要绑定的n个值到栈上,然后调用lua_pushcclosure(L,fn,n)这样就形成的一个c闭包

无返回值

栈–(n-1),一共弹出n个元素(及那些绑定的值),压入一个cclosure

#definelua_pushcfunction(L,f)lua_pushcclosure(L,f,0)

#definelua_register(L,n,f)(lua_pushcfunction(L,f),lua_setglobal(L,n))

没有返回值

栈不变化

这个是比较常用的,以n为lua中的key压入一个0个绑定值的cclosure.

--------------------------------------------------------------------------------------------------------

这次主要记录lua函数调用的几个相关的api,需要说明的是,对于这几个api,lua手册上写的相当详细,相当好,可以直接看手册

10.调用一个lua函数

voidlua_call(lua_State*L,intnargs,intnresults)

luacapi的特点就是"不是一个人在战斗"[我想表达的意思是,lua中的一句话,在capi实现起来就是n句,可能有人疑惑那为什么不直接用lua多好,capi这么麻烦,答案是有的事只能用capi才能实现],所以,调用它之前,需要布局一下栈,第一,要把要call的函数压入栈;第二,call要用的参数正序压入栈中;然后才能调用lua_call,调用完了,自己去取返回值,它都给你压栈上了.

操作:

argn=Stack.pop()

...//一共压入nargs个参数

arg2=Stack.pop()

arg3=Stack.pop()

func=Stack.pop()//函数本身也弹出

res1,res2,...,resj=func(arg1,arg2,...,argn)

Stack.push(res1)

Stack.push(res2)

…//压入nresults个返回值

Stack.push(resj)

无返回值

调用结束后,栈高度增加nresults–(1+nargs),如果将nresults参数设置为LUA_MULTRET,那么lua返回几个值,栈上就压入几个值,否者强制压入nresults个值,不足的是空值,多余的抛弃掉

注意,这个函数是有危险的,如果在其中发生了错误,会直接退出程序

这个函数的用途:尚未发现,除非你能接受出错立马退出,反正我是做游戏的,我受不起,呵呵,顺便一说,lauxlib.h中的luaL_check*一族函数也是这样的,不符合预期的话,直接退出,这些函数都要小心,有类似于断言的效果.

11.保护下调用一个lua函数

intlua_pcall(lua_State*L,intnargs,intnresults,interrfunc)

参数,行为和lua_call都一样,如果在调用中没有发生任何错误,lua_pcall==lua_call;但是如果有错误发生时,lua_pcall会捕获它

errfunc指出了Stack上的一个元素,这个元素应该是一个函数,当发生错误的时候

ef=Stack[errfunc]

value=ef(errmsg)

Stack.push(value)

也就是说,在错误的时候,errfunc指定的错误处理函数会被调用,该处理函数的返回值被压到栈上.

默认情况下,可以给errfunc传值0,实际的效果是指定了这样一个函数做出错处理functiondefaulterr(errmsg)returnerrmsgend.

本函数有返回值LUA_ERRRUN运行时错误LUA_ERRMEM内存分配错误[注意,这种错会导致lua调用不了错误处理函数]LUA_ERRERR运行错误处理函数时出错了,写程序的时候必须检查返回值:)

强烈推荐该函数,不过事实上大家也都用的这个函数:)

12.保护下调用一个c函数

intlua_cpcall(lua_State*L,lua_CFunctionfunc,void*ud)以保护模式调用c函数,func中可以且只能从堆栈上拿到一个参数,就是ud,当有错误时,和lua_pcall返回相同的错误代码,并在堆栈顶部留下errmsg字符串,调用成功的话它返回零,并且不会修改堆栈,所有从func中返回的值都被扔掉.

这里注意的问题是:

1."当有错误时",这个错误的意思是lua的错误,而不是c/c++的错误.在func中使用lua_call和lua_check*族函数,并不会导致程序退出了,而是表现的像lua_pcall那样.

2.调用成功的时候func中的返回值都被扔掉了.

相关推荐