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()一定要将数据完全取出后保存到你的别的变量中去,否则会因为清栈操作,导致你的程序异常,切记!

posted @ 2014-10-13 11:56  小天_y  阅读(1731)  评论(0编辑  收藏  举报