Lua脚本和C++交互(二)
上一节讲了一些基本的Lua应用,下面,我要强调一下,Lua的栈的一些概念,因为这个确实很重要,你会经常用到。熟练使用Lua,最重要的就是要时刻知道什么时候栈里面的数据是什么顺序,都是什么。如果你能熟练知道这些,实际你已经是Lua运用的高手了。当你初始化一个栈的时候,它的栈底是1,而栈顶相对位置是-1,说形象一些,你可以把栈想象成一个环,有一个指针标记当前位置,如果-1,就是当前栈顶,如果是-2就是当前栈顶前面一个参数的位置。以此类推。当然,你也可以正序去取,这里要注意,对于Lua的很多API,下标是从1开始的。这个和C++有些不同。而且,在栈的下标中,正数表示绝对栈底的下标,负数表示相对栈顶的相对地址,这个一定要有清晰的概念,否则很容易看晕了。(栈中数据的存储方式如下图)
注:其实都是从栈底到栈顶,索引数值递增。
下面看一些具体的例子:
lua_pushnumber(m_pState, 11); lua_pushnumber(m_pState, 12); // lua_gettop()这个API是告诉你目前栈里元素的个数 int nIn = lua_gettop(m_pState) int nData1 = lua_tonumber(m_pState, 1); //读取栈底第一个绝对坐标中的元素 int nData2 = lua_tonumber(m_pState, 2); //读取栈底第二个绝对坐标中的元素 int nData3 = lua_tonumber(m_pState, -1); //读取栈顶第一个相对坐标中的元素 int nData4 = lua_tonumber(m_pState, -2); //读取栈顶第二个相对坐标中的元素 printf("[Test]nData1 = %d, nData2 = %d./n, nData3 = %d, nData4 = %d./n");
[Test]nData1 = 11, nData2 = 12, nData3 = 12, nData4 = 11
上述代码的栈中存储如下图:
回到我上一节的那个代码:
bool CLuaFn::CallFileFn(const char* pFunctionName, int nParam1, int nParam2) { int nRet = 0; if (NULL == m_pState) { printf("[CLuaFn::CallFileFn]m_pState is NULL./n"); return false; } //验证你的Lua函数是否在你当前加载的Lua文件中,并把指针指向这个函数位置 lua_getglobal(m_pState, pFunctionName); // 压栈(顺序:从左到右的参数):第一个参数 lua_pushnumber(m_pState, nParam1); // 压栈:第二个参数 lua_pushnumber(m_pState, nParam2); int nIn = lua_gettop(m_pState); //在这里加一行 //执行这个函数,2是输入参数的个数,1是返回值的个数 nRet = lua_pcall(m_pState, 2, 1, 0); if (nRet != 0) { printf("[CLuaFn::CallFileFn]call function(%s) error(%d)./n", pFunctionName, nRet); return false; } if (lua_isnumber(m_pState, -1) == 1) { int nSum = lua_tonumber(m_pState, -1); printf("[CLuaFn::CallFileFn]Sum = %d./n", nSum); } int nOut = lua_gettop(m_pState); //在这里加一行 return true; }
nIn的答案是多少?或许你会说是2吧,呵呵,实际是3。或许你会问,为什么会多一个?其实我第一次看到这个数字,也很诧异。但是确实是3。因为你调用的函数名称占据了一个堆栈的位置。其实,在获取nIn那一刻,堆栈的样子是这样的(函数接口地址,参数1,参数2),函数名称也是一个变量入栈的。而nOut输出是1,lua_pcall()函数在调用成功之后,会自动的清空栈,然后把结果放入栈中。在获取nOut的一刻,栈内是这幅摸样(输出参数1)。
这里就要再迁出一个更重要的概念了,Lua不是C++,对于C++程序员而言,一个函数会自动创建栈,当函数执行完毕后会自动清理栈,Lua可不会给你这么做,对于Lua而言,它没有函数这个概念,一个栈对应一个lua_State指针,也就是说,你必须手动去清理你不用的栈,否则会造成垃圾数据占据你的内存。
不信?那么咱们来验证一下,就拿昨天的代码吧,你用for循环调用100万次。看看nOut的输出结果。。我相信,程序执行不到100万次就会崩溃,而你的内存也会变的硕大无比。而nOut的输出也会是这样的 1,2,3,4,5,6。。。。。
原因就是,Lua不会清除你以前栈内的数据,每调用一次都会给你生成一个新的栈元素插入其中。
#include "CLuaFn.h" int main(int argc, char* argv[]) { CLuaFn LuaFn; LuaFn.Init(); LuaFn.LoadLuaFile("Sample.lua"); for (int i = 1; i < 100; i ++) { LuaFn.CallFileFn("func_Add", 11, 12); } getchar(); return 0; } bool CLuaFn::CallFileFn(const char* pFunctionName, int nParam1, int nParam2) { int nRet = 0; if (NULL == m_pState) { printf("[CLuaFn::CallFileFn]m_pState is NULL./n"); return false; } //验证你的Lua函数是否在你当前加载的Lua文件中,并把指针指向这个函数位置 lua_getglobal(m_pState, pFunctionName); // 压栈(顺序:从左到右的参数):第一个参数 lua_pushnumber(m_pState, nParam1); // 压栈:第二个参数 lua_pushnumber(m_pState, nParam2); int nIn = lua_gettop(m_pState); //在这里加一行 //执行这个函数,2是输入参数的个数,1是返回值的个数 nRet = lua_pcall(m_pState, 2, 1, 0); if (nRet != 0) { printf("[CLuaFn::CallFileFn]call function(%s) error(%d)./n", pFunctionName, nRet); return false; } if (lua_isnumber(m_pState, -1) == 1) { int nSum = lua_tonumber(m_pState, -1); printf("[CLuaFn::CallFileFn]Sum = %d./n", nSum); } int nOut = lua_gettop(m_pState); //在这里加一行 //lua_settop(m_pState, -2); //清除不用的栈 printf("[CLuaFn::CallFileFn]nOut = %d.\n", nOut); return true; }
那么怎么解决呢?呵呵,其实,如果不考虑多线程的话,在你的函数最后退出前加一句话,就可以轻松解决这个问题。(Lua栈操作是非线程安全的!)lua_settop(m_pState, -2);这句话的意思是什么?lua_settop()是设置栈顶的位置,我这么写,意思就是,栈顶指针目前在当前位置的-2的元素上。这样,我就实现了对栈的清除。仔细想一下,是不是这个道理呢?(运行结果如下图所示)
最后说一句,lua_tonumber()或lua_tostring()还有以后我们要用到的lua_touserdata()一定要将数据完全取出后保存到你的别的变量中去,否则会因为清栈操作,导致你的程序异常,切记!