浅析CC++和Lua的通信方式
为了实现lua和其他语言之间的通信,lua虚拟机为c\c++提供了两个特性:
一,lua_state状态机
lua_state主要是管理一个lua虚拟机的执行环境, 一个lua虚拟机可以有多个执行环境。lua虚拟机通过维护这样一个虚拟栈来实现两种之间的通信,lua_state定义如下:
struct lua_state { commonheader; lu_byte status; stkid top; /* first free slot in the stack */ global_state *l_g; callinfo *ci; /* call info for current function */ const instruction *oldpc; /* last pc traced */ stkid stack_last; /* last free slot in the stack */ stkid stack; /* stack base */ int stacksize; unsigned short nny; /* number of non-yieldable calls in stack */ unsigned short nccalls; /* number of nested c calls */ lu_byte hookmask; lu_byte allowhook; int basehookcount; int hookcount; lua_hook hook; gcobject *openupval; /* list of open upvalues in this stack */ gcobject *gclist; struct lua_longjmp *errorjmp; /* current error recover point */ ptrdiff_t errfunc; /* current error handling function (stack index) */ callinfo base_ci; /* callinfo for first level (c calling lua) */ };
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定义如下:
struct lua_tvalue { tvaluefields; }; #define tvaluefields \ union { struct { value v__; int tt__; } i; double d__; } u union value { gcobject *gc; /* collectable objects */ void *p; /* light userdata */ int b; /* booleans */ lua_cfunction f; /* light c functions */ numfield /* numbers */ };
lua有自己的gc,c\c++由自己申请和释放内存,所以两者之间的内存管理是独立的。从c\c++中传递数据到lua虚拟机会发生数据拷贝,从lua虚拟机中传递出来是直接从虚拟栈中取值或者地址,所以数据从虚拟栈中pop之后,是否依然是有效引用需要额外注意。
二,c api
lua脚本实现交互提供了一系列的c api,常用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类型,都有一个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得出。
出栈操作:
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而言,不会检查值是否为数字类型,而是检查值是否能转换为数字类型。
如有任何疑问和建议,欢迎指出讨论,谢谢~