上一节讲了一些基本的Lua应用,或许你会说,仍是很简单么。呵呵,恩,是的,原本Lua就是为了让你们使用的方便快捷而设计的。若是设计的过为复杂,就不会有人使用了。linux
下面,我要强调一下,Lua的栈的一些概念,由于这个确实很重要,你会常常用到。熟练使用Lua,最重要的就是要时刻知道何时栈里面的数据是什么顺序,都是什么。若是你能熟练知道这些,实际你已是Lua运用的高手了。程序员
说 真的,第一次我接触栈的时候,没有把它想的很复杂,却是看了网上不少的关于Lua的文章让我对栈的理解云里雾里,什么元表,什么User,什么局部变量, 什么全局变量位移。说的那叫一个晕。本人脑子笨,理解不了这么多,也不知道为何不少人喜欢把Lua栈弄的七上八下,代码晦涩难懂。后来实在受不了了,去 Lua网站下载了Lua的文档,写的很清晰。Lua的栈实际上几句话足以。windows
当你初始化一个栈的时候,它的栈底是1,而栈顶相对位置是-1,说形 象一些,你能够把栈想象成一个环,有一个指针标记当前位置,若是-1,就是当前栈顶,若是是-2就是当前栈顶前面一个参数的位置。以此类推。安全
固然,你也可 以正序去取,这里要注意,对于Lua的不少API,下标是从1开始的。这个和C++有些不一样。并且,在栈的下标中,正数表示绝对栈底的下标,负数表示相对 栈顶的相对地址,这个必定要有清晰的概念,不然很容易看晕了。 让咱们看一些例子,加深理解。多线程
lua_pushnumber(m_pState, 11); lua_pushnumber(m_pState, 12); int nIn = lua_gettop(m_pState); <–这里加了一行, lua_gettop()这个API是告诉你目前栈里元素的个数。 若是仅仅是Push两个参数,那么nIn的数值是2,对。没错。那么我们看看栈里面是怎么放的。我再加两行代码。 lua_pushnumber(m_pState, 11); lua_pushnumber(m_pState, 12); int nIn = lua_gettop(m_pState) int nData1 = lua_tonumber(m_pState, 1); <–读取栈底第一个绝对坐标中的元素 int nData2 = lua_tonumber(m_pState, 2); <–读取栈底第二个绝对坐标中的元素 printf(“[Test]nData1 = %d, nData2 = %d./n”);
若是是你,凭直觉,告诉我答案是什么?函数
如今公布答案,看看是否是和你想的同样。工具
[Test]nData1 = 11, nData2 = 12
那么,若是我把代码换成网站
lua_pushnumber(m_pState, 11); lua_pushnumber(m_pState, 12); int nIn = lua_gettop(m_pState) int nData1 = lua_tonumber(m_pState, -1); <–读取栈顶第一个相对坐标中的元素 int nData2 = lua_tonumber(m_pState, -2); <–读取栈顶第二个相对坐标中的元素 printf(“[Test]nData1 = %d, nData2 = %d./n”);
请你告诉我输出是什么? 答案是lua
[Test]nData1 = 12, nData2 = 11
呵呵,挺简单的吧,对了,其实就这么简单。网上其它的高阶运用,其实大部分都是对栈的位置进行调整。只要你抓住主要概念,看懂仍是不难的。什么元表,什么变量,其实都同样,抓住核心,时刻知道栈里面的样子,就没有问题。spa
好了,回到我上一节的那个代码。
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_getglobal(m_pState, pFunctionName); lua_pushnumber(m_pState, nParam1); lua_pushnumber(m_pState, nParam2); int nIn = lua_gettop(m_pState); <–在这里加一行。 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不会清除你之前栈内的数据,每调用一次都会给你生成一个新的栈元素插入其中。
那么怎么解决呢?呵呵,其实,若是不考虑多线程的话,在你的函数最后退出前加一句话,就能够轻松解决这个问题。(Lua栈操做是非线程安全的!)
lua_settop(m_pState, -2);
这句话的意思是什么?lua_settop()是设置栈顶的位置,我这么写,意思就是,栈顶指针目前在当前位置的-2的元素上。这样,我就实现了对栈的清除。仔细想一下,是否是这个道理呢?
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_getglobal(m_pState, pFunctionName); lua_pushnumber(m_pState, nParam1); lua_pushnumber(m_pState, nParam2); int nIn = lua_gettop(m_pState); <–在这里加一行。 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); <–清除不用的栈。 return true; }
好了,再让咱们运行100万次,看看你的程序内存,看看你的程序还崩溃不? 若是你想打印 nOut的话,输出会变成1,1,1,1,1。。。。
最后说一句,lua_tonumber()或lua_tostring()还有之后咱们要用到的lua_touserdata()必定要将数据彻底取出后保存到你的别的变量中去,不然会由于清栈操做,致使你的程序异常,切记!
呵呵,说了这么多,主要是让你们如何写一个严谨的Lua程序,不要运行没两下就崩溃了。好了,基础栈的知识先说到这里,之后还有一些技巧的运用,到时候会给你们展现。
下面说一下,Lua的工具。(为何要说这个呢?呵呵,由于咱们下一步要用到其中的一个帮助咱们的开发。)
呵呵,其实,Lua里面有不少简化开发的工具,你能够去http://www.sourceforge.net/去找一下。它们可以帮助你简化C++对象与Lua对象互转之间的代码。
这里说几个有名的,固然可能不全。
(lua tinker)若是你的系统在windows下,并且不考虑移植,那么我强烈推荐你去下载一个叫作lua tinker的小工具,整个工具很是简单,一个.h和一个.cpp。直接就能够引用到你的工程中,连独立编译都不用,这是一个韩国人写的Lua与 C++接口转换的类,十分方便,代码简洁(居家旅行,必备良药)。
它是基于模板的,因此你能够很轻松的把你的C对象绑定到Lua中。代码较长,呵呵, 有兴趣的朋友能够给我留言索要lua tinker的例子。就不贴在这里了。不过我我的不推荐这个东西,由于它在Linux下是编译不过去的。它使用了一种g不支持的模板写法,虽然有人在 尝试把它修改到Linux下编译,但据我所知,修改后效果较好的彷佛尚未。不过若是你只是在 windows下,那就没什么可犹豫的,强烈推荐,你会喜欢它的。
(Luabinder)相信用过Boost库的朋友,或许对这个家伙很熟悉。它是一个很强大的Linux下Lua扩展包,帮你封装了不少Lua的复 杂操做,主要解决了绑定C++对象和Lua对象互动的关系,很是强大,不过嘛,对于freeeyes而言,仍是不推荐,由于freeeyes很懒,不想为 了一个Lua还要去编译一个庞大的boost库,固然,见仁见智,若是你的程序自己就已经加载了boost,那么就应该坚决果断的选择它。
(lua++)呵呵,这是我最喜欢,也是我一直用到如今的库,比较前两个而言,lua++的封装性没有那么好,不少东西仍是须要一点代码的,不过之 因此我喜欢,是由于它是用C写的,能够在windows下和linux下轻松转换
还记得我昨天说过如何编译Lua么,如今请你再作一遍,不一样的是,请把lua的程序包中的src/lib中的全部h和cpp,还有 include下的那个.h拷贝到你上次创建的lua工程中。而后所有添加到你的静态连接库工程中去,从新编译。会生成一个新的lua.lib,这个 lua就自动包含了lua的功能。最后记得把tolua++.h放在你的Include文件夹下。 行了,咱们把上次CLuaFn类稍微改一下。
extern “C” { #include “lua.h” #include “lualib.h” #include “lauxlib.h” #include “tolua++” //这里加一行 }; class CLuaFn { public: CLuaFn(void); ~CLuaFn(void); void Init(); //初始化Lua对象指针参数 void Close(); //关闭Lua对象指针 bool LoadLuaFile(const char* pFileName); //加载指定的Lua文件 bool CallFileFn(const char* pFunctionName, int nParam1, int nParam2); //执行指定Lua文件中的函数 private: lua_State* m_pState; //这个是Lua的State对象指针,你能够一个lua文件对应一个。 };
行了,这样咱们就能用Lua++下的功能了。
你们看到了 bool CallFileFn(const char* pFunctionName, int nParam1, int nParam2);这个函数的运用。演示了真么调用Lua函数。
下面,我改一下,这个函数。为何?仍是由于freeeyes很懒,我可不想每有一个函数,我都要写一个C++函数去调用,太累!我要写一个通用的!支持任意函数调用的接口!
因而我建立了两个类。支持任意参数的输入和输出,并打包送给lua去执行,说干就干。
#ifndef _PARAMDATA_H #define _PARAMDATA_H #include <vector> #define MAX_PARAM_200 200 using namespace std; struct _ParamData { public: void* m_pParam; char m_szType[MAX_PARAM_200]; int m_TypeLen; public: _ParamData() { m_pParam = NULL; m_szType[0] = ‘/0′; m_TypeLen = 0; }; _ParamData(void* pParam, const char* szType, int nTypeLen) { SetParam(pParam, szType, nTypeLen); } ~_ParamData() {}; void SetParam(void* pParam, const char* szType, int nTypeLen) { m_pParam = pParam; sprintf(m_szType, “%s”, szType); m_TypeLen = nTypeLen; }; bool SetData(void* pParam, int nLen) { if(m_TypeLen < nLen) { return false; } if(nLen > 0) { memcpy(m_pParam, pParam, nLen); } else { memcpy(m_pParam, pParam, m_TypeLen); } return true; } void* GetParam() { return m_pParam; } const char* GetType() { return m_szType; } bool CompareType(const char* pType) { if(0 == strcmp(m_szType, pType)) { return true; } else { return false; } } }; class CParamGroup { public: CParamGroup() {}; ~CParamGroup() { Close(); }; void Init() { m_vecParamData.clear(); }; void Close() { for(int i = 0; i < (int)m_vecParamData.size(); i++) { _ParamData* pParamData = m_vecParamData; delete pParamData; pParamData = NULL; } m_vecParamData.clear(); }; void Push(_ParamData* pParam) { if(pParam != NULL) { m_vecParamData.push_back(pParam); } }; _ParamData* GetParam(int nIndex) { if(nIndex < (int)m_vecParamData.size()) { return m_vecParamData[nIndex]; } else { return NULL; } }; int GetCount() { return (int)m_vecParamData.size(); } private: typedef vector<_ParamData*> vecParamData; vecParamData m_vecParamData; }; #endif #endif
我建立了两个类,把Lua要用到的类型,数据都封装起来了。这样,我只须要这么改写这个函数。 bool CallFileFn(const char* pFunctionName, CParamGroup& ParamIn, CParamGroup& ParamOut); 它就能按照不一样的参数自动给我调用,嘿嘿,懒到家吧!
其实这两个类很简单,_ParamData是参数类,把你要用到的参数放入到这个对象中去,标明类型的大小,类型名称,内存块。而CParamGroup负责将不少不少的_ParamData打包在一块儿,放在vector里面。
好了,让咱们看看CallFileFn函数里面我怎么改的。
bool CLuaFn::CallFileFn(const char* pFunctionName, CParamGroup& ParamIn, CParamGroup& ParamOut) { int nRet = 0; int i = 0; if(NULL == m_pState) { printf(“[CLuaFn::CallFileFn]m_pState is NULL./n”); return false; } lua_getglobal(m_pState, pFunctionName); //加载输入参数 for(i = 0; i < ParamIn.GetCount(); i++) { PushLuaData(m_pState, ParamIn.GetParam(i)); } nRet = lua_pcall(m_pState, ParamIn.GetCount(), ParamOut.GetCount(), 0); if (nRet != 0) { printf(“[CLuaFn::CallFileFn]call function(%s) error(%s)./n”, pFunctionName, lua_tostring(m_pState, -1)); return false; } //得到输出参数 int nPos = 0; for(i = ParamOut.GetCount() – 1; i >= 0; i–) { nPos–; PopLuaData(m_pState, ParamOut.GetParam(i), nPos); } int nCount = lua_gettop(m_pState); lua_settop(m_pState, -1-ParamOut.GetCount()); return true; }
别的没变,加了两个循环,由于考虑lua是能够支持多结果返回的,因此我也作了一个循环接受参数。
lua_settop(m_pState, -1-ParamOut.GetCount());这句话是否是有些意思,恩,是的,我这里作了一个小技巧,由于我不知道返回参数有几个,因此我会根据返回参数的个数从新设置栈顶。这样作能够返回任意数量的栈并且清除干净。
或许细心的你已经发现,里面多了两个函数。恩,是的。来看看这两个函数在干什么。
bool CLuaFn::PushLuaData(lua_State* pState, _ParamData* pParam) { if(pParam == NULL) { return false; } if(pParam->CompareType(“string”)) { lua_pushstring(m_pState, (char* )pParam->GetParam()); return true; } if(pParam->CompareType(“int”)) { int* nData = (int* )pParam->GetParam(); lua_pushnumber(m_pState, *nData); return true; } else { void* pVoid = pParam->GetParam(); tolua_pushusertype(m_pState, pVoid, pParam->GetType()); return true; } }
参数入栈操做,呵呵,或许你会问tolua_pushusertype(m_pState, pVoid, pParam->GetType());这句话,你可能有些看不懂,不要紧,我会在下一讲详细的解释Lua++的一些API的用法。
如今大概和你说 一下,这句话的意思就是,把一个C++对象传输给Lua函数。 再看看,下面一个。
bool CLuaFn:: PopLuaData(lua_State* pState, _ParamData* pParam, int nIndex) { if(pParam == NULL) { return false; } if(pParam->CompareType(“string”)) { if (lua_isstring(m_pState, nIndex) == 1) { const char* pData = (const char*)lua_tostring(m_pState, nIndex); pParam->SetData((void* )pData, (int)strlen(pData)); } return true; } if(pParam->CompareType(“int”)) { if (lua_isnumber(m_pState, nIndex) == 1) { int nData = (int)lua_tonumber(m_pState, nIndex); pParam->SetData(&nData, sizeof(int)); } return true; } else { pParam->SetData(tolua_tousertype(m_pState, nIndex, NULL), -1); return true; } }
弹出一个参数并赋值。pParam->SetData(tolua_tousertype(m_pState, nIndex, NULL), -1);这句话一样,我在下一讲中详细介绍。
好了,咱们又进了一步,咱们能够用这个函数绑定任意一个Lua函数格式。而代码不用多写,懒蛋的目的达到了。
这一讲主要是介绍了一些基本知识,或许有点多余,可是我以为是必要的,在下一讲中,我讲开始详细介绍如何绑定一个C++对象给Lua,并让Lua对其修改。而后返回结果。
小伙伴们,还请持续关注更新,更多干货和资料请直接联系我,也能够加群710520381,邀请码:柳猫,欢迎你们共同讨论