浅析C\C++和Lua的通信方式
为了实现Lua和其他语言之间的通信,Lua虚拟机为C\C++提供了两个特性:
一,Lua_State状态机
lua_State主要是管理一个lua虚拟机的执行环境,一个lua虚拟机可以有多个执行环境。Lua虚拟机通过维护这样一个虚拟栈来实现两种之间的通信,lua_State定义如下:
structlua_State{ CommonHeader; lu_bytestatus; StkIdtop;/*firstfreeslotinthestack*/ global_State*l_G; CallInfo*ci;/*callinfoforcurrentfunction*/ constInstruction*oldpc;/*lastpctraced*/ StkIdstack_last;/*lastfreeslotinthestack*/ StkIdstack;/*stackbase*/ intstacksize; unsignedshortnny;/*numberofnon-yieldablecallsinstack*/ unsignedshortnCcalls;/*numberofnestedCcalls*/ lu_bytehookmask; lu_byteallowhook; intbasehookcount; inthookcount; lua_Hookhook; GCObject*openupval;/*listofopenupvaluesinthisstack*/ GCObject*gclist; structlua_longjmp*errorJmp;/*currenterrorrecoverpoint*/ ptrdiff_terrfunc;/*currenterrorhandlingfunction(stackindex)*/ CallInfobase_ci;/*CallInfoforfirstlevel(CcallingLua)*/ };
1,虚拟栈的管理,包括管理整个栈和当前函数使用的栈的情况
2,CallInfo的管理,包括管理整个CallInfo数组和当前函数的CallInfo
3,hook相关的,包括hookmask,hookcount,hook函数等
4,global_State是全局唯一的,存放多个lua_State之间的一些共享数据
5,gc的一些管理和当前栈中upvalue的管理
6,错误处理的支持等等
C\C++和Lua拥有不同的数据类型,要实现两者之间的数据通信怎么办?Lua虚拟机提供Lua_State这样一种数据结构。任何一种数据从C\C++传入Lua虚拟机中,Lua都会将这类数据转换为一种通用的结构lua_TValue,并且将数据复制一份,将其压入虚拟栈中。lua_TValue定义如下:
structlua_TValue{ TValuefields; }; #defineTValuefields\ union{struct{Valuev__;inttt__;}i;doubled__;}u unionValue{ GCObject*gc;/*collectableobjects*/ void*p;/*lightuserdata*/ intb;/*booleans*/ lua_CFunctionf;/*lightCfunctions*/ numfield/*numbers*/ };
Lua有自己的GC,C\C++由自己申请和释放内存,所以两者之间的内存管理是独立的。从C\C++中传递数据到Lua虚拟机会发生数据拷贝,从Lua虚拟机中传递出来是直接从虚拟栈中取值或者地址,所以数据从虚拟栈中pop之后,是否依然是有效引用需要额外注意。
二,CAPI
Lua脚本实现交互提供了一系列的CAPI,常用API有:
luaL_newstate函数用于初始化一个lua_State实例
luaL_openlibs函数用于打开Lua中的所有标准库,如io库、string库等。
luaL_loadbuffer编译了buff中的Lua代码,如果没有错误,则返回0,同时将编译后的程序块压入虚拟栈中。
lua_pcall函数会将程序块从栈中弹出,并在保护模式下运行该程序块。执行成功返回0,否则将错误信息压入栈中。
lua_tostring函数中的-1,表示栈顶的索引值,栈底的索引值为1,以此类推。该函数将返回栈顶的错误信息,但是不会将其从栈中弹出。
lua_pop是一个宏,用于从虚拟栈中弹出指定数量的元素,这里的1表示仅弹出栈顶的元素。
lua_close用于释放状态指针所引用的资源。
入栈操作:
Lua针对每种C类型,都有一个CAPI函数与之对应,如:
voidlua_pushnil(lua_State*L); --nil值
voidlua_pushboolean(lua_State*L,intb);--布尔值
voidlua_pushnumber(lua_State*L,lua_Numbern);--浮点数
voidlua_pushinteger(lua_State*L,lua_Integern); --整型
voidlua_pushlstring(lua_State*L,constchar*s,size_tlen);--指定长度的内存数据
voidlua_pushstring(lua_State*L,constchar*s); --以零结尾的字符串,其长度可由strlen得出。
出栈操作:
API使用“索引”来引用栈中的元素,第一个压入栈的为1,第二个为2,依此类推。我们也可以使用负数作为索引值,其中-1表示为栈顶元素,-2为栈顶下面的元素,同样依此类推。
Lua提供了一组特定的函数用于检查返回元素的类型,如:
intlua_isboolean(lua_State*L,intindex);
intlua_iscfunction(lua_State*L,intindex);
intlua_isfunction(lua_State*L,intindex);
intlua_isnil(lua_State*L,intindex);
intlua_islightuserdata(lua_State*L,intindex);
intlua_isnumber(lua_State*L,intindex);
intlua_isstring(lua_State*L,intindex);
intlua_istable(lua_State*L,intindex);
intlua_isuserdata(lua_State*L,intindex);
以上函数,成功返回1,否则返回0。需要特别指出的是,对于lua_isnumber而言,不会检查值是否为数字类型,而是检查值是否能转换为数字类型。
如有任何疑问和建议,欢迎指出讨论,谢谢~