Lua的调用原理(Lua的堆栈)
Lua栈
什么是Lua栈
Lua的栈类似于以下定义,它是在创建lua_State的时候被创建的:
TValue stack[max_stack_len]//欲知内情可以查看lstate.c文件中的stack_init函数。
存入栈的数据结构包含数值、字符串、指针、table、闭包等。
通过Lua栈实现和C/C++的通讯
Lua和C通信的约定
“如果你想要什么,你告诉我,我来产生“就可以保证,凡是Lua中的变量,Lua要负责这些变量的生命周期和垃圾回收,所以,必须由Lua来创建这些值(在创建时就加入了生命周期对其进行管理)。
”然后放到栈上,你只能通过API来操作这个值“,Lua的API给C提供了一套完备的操作界面,这个就相当于约定的通信协议,如果Lua客户使用这个操作界面,那么Lua本身不会出现任何”意料之外“的错误。
”我只管我的世界“这句话体现了Lua和C/C++作为两个不同系统的分界,C/C++中的值,Lua是不知道的,Lua只负责它的世界。
栈的索引规则
栈底到栈顶索引是+1递增的规律,同时索引有正数索引和负数索引两种表达方式:
- 正数索引,不需要知道栈的大小,我们就能知道栈底在哪,栈底的索引永远是1。
即:栈底是1,然后一直到栈顶逐渐+1。 - 负数索引,不需要知道栈的大小,我们就能知道栈顶在哪,栈顶的索引永远是-1。
即:栈顶是-1,然后一直到栈底逐渐-1。
Lua和C++通讯实例
实际上Lua本质是和C通讯的,C++支持extern “C”。
假设在一个Lua文件中有如下定义:
-- hello.lua文件
myName = "beauty girl"
想要在C++中获取到myName的值,可以使用lua_getglobal来获取:
/*获取table变量,在栈顶*/
lua_getglobal(pL,"myName");
lua_getglobal的处理过程如下:(请注意红色数字,代表通信顺序)
- C++想要获取Lua的myName字符串的值,所以它把myName放到Lua堆栈(栈顶),以便Lua能看到。
- Lua从堆栈(栈顶)中获取myName,此时栈顶再次变为空。
- Lua拿着这个myName去Lua全局变量查找myName对应的字符串。
- Lua把取得的“beauty girl”字符串放到堆栈(栈顶)。
- C++可以从Lua堆栈中取得“beauty girl”。
现在,我们给hello.lua文件中添加一个table全局变量:
-- hello.lua文件
myName = "beauty girl"
helloTable = {name = "mutou",IQ = 125}
我们看到,多了一个helloTable的变量,他和数组十分相似,又和HashMap有点类似,总之它很强大。
获取helloTable变量的方式和以前是一样的:
/* 取得table变量,在栈顶 */
lua_getglobal(pL, "helloTable");
这样,helloTable变量就被存放到栈顶。
可我们并不是要取table变量,因为C++中是无法识别Lua的table类型的,所以我们要取得table中具体的值,也就是name和IQ的值。
有一个和lua_getglobal类似的函数,叫做lua_gettable,顾名思义,它是用来取得table相关的数据的。
lua_gettable函数调用后,Lua会从栈顶(注意是栈顶,即如果想同时push进入两个key值,那么根据栈的原理,先进后出,那么后push进入的key值对应的value值先取,替换的值永远放在栈顶!)取得一个key值,然后根据这个key值去栈中指定的table中寻找对应的value值,最后把找到的value值放到栈顶,同时栈中key值将被移除。
lua_pushstring()函数可以把C++中的字符串存放到Lua的栈里,然后再用lua_gettable()去执行前面所说的步骤,lua_gettable的第二个参数是指定的table变量在栈中的索引。
为了方便理解,我们画个图来表示:
这是初始状态,堆栈里还没有任何东西,那么,现在要先把helloTable变量放到栈顶:
/* 取得table变量,在栈顶 */
lua_getglobal(pL, "helloTable");
然后就变成了这样:
接着,我们要取得table的name对应的值,那么,先要做的是把“name”字符串入栈:
/*将C++的字符串放到Lua的栈中,此时,栈顶变为“name”,helloTable的对象变为栈底*/
lua_pushstring(pL,"name");
然后变成这样:
注意,这里加上相应的索引,采用负数索引。
由于“name”的入栈,现在helloTable变量已经不在栈顶。接着,我们调用要做的最重要的一步,取得name在table中对应的值。
/*从table对象寻找“name”对应的值(table对象现在在索引为-2的栈中,也就是当前的栈底),取得对应值之后,取代"name"返回栈顶。*/
lua_gettable(pL,-2);
此时,栈变成这样:
完整实例:
int main (int argc, char **argv) {
// 创建一个lua虚拟机
lua_State* L = luaL_newstate();
// 加载一个常用的系统库
luaL_openlibs(L);
// 加载lua文件并执行
if (luaL_dofile(L, "main.lua")) {
printf("%s\n", lua_tostring(L, -1));
}
// 取得table变量,在栈顶
lua_getglobal(L, "helloTable");
int top = lua_gettop(L);
printf("%d\n", top);
// 将C字符串放到Lua堆栈中,此时栈顶为"name",tbTable对象在栈底
lua_pushstring(L, "name");
top = lua_gettop(L);
printf("%d\n", top);
// 从tbTable对象中寻找"name"对应的值(tbTable对象现在在负数索引为-2【正数索引为1】
// 的栈中,也就是栈底),取得对应的值后,用value值取代key值返回栈顶!!
lua_gettable(L, -2);
top = lua_gettop(L);
printf("%d\n", top);
const char* value = lua_tostring(L, -1);
printf("%s\n", value);
top = lua_gettop(L);
printf("%d\n", top);
if (LUA_TTABLE == lua_type(L, -2)) {
printf("table\n");
}
if (LUA_TSTRING == lua_type(L, -1)) {
printf("string\n");
}
// 关闭lua虚拟机
lua_close(L);
return 0;
}
输出结果:
lua_gettable到底做了什么事情?
首先,我们来解释一下lua_gettable的第二个参数,-2是什么意思,-2就是刚刚helloTable变量在栈中的索引。然后,Lua会去取得栈顶的值(之前的栈顶是“name”),然后拿着这个值去helloTable变量中寻找对应的值,当然就找到“mutou”了。
最后,Lua会把找到的值入栈,于是“mutou”就到了栈顶。