LUA学习笔记
?
LUA学习笔记 本文从Programming in Lua中提取 使用LUA的目的: 当你真正要在项目中使用LUA时会明白自己的选择: 为了程序的可扩展性和随意性,减少后期需求的变化进项目造成的影响. ? 本文只介绍一些很肤浅关于LUA的C环境构建,及一些简单的LUA函数应用,是自己使用LUA的总结和入门记录. ? LUA将给程序带来的功能: 1.LUA可以当一个配置文件使用(读写LUA全局变量) 2.程序中调用LUA定义的函数(C函数调用lua) 3.LUA可以调用自定义的C函数 (lua调用C函数) 4.运行LUA文件的函数(调用LUA文件运行函数) 5.运行LUA函数的函数(调用LUA代码片断的函数) 以上是LUA的一些基本应用,也是一个构建LUA的C环境必须实现的基本功能,主要是C程序和LUA的几个交互.下面会一一介绍,对于LUA的构架还是很必要的 ? LUA的总体认识: 头文件lua.h定义了Lua提供的基础函数。其中包括创建一个新的Lua环境的函数(如lua_open),调用Lua函数(如lua_pcall)的函数,读取/写入Lua环境的全局变量的函数,注册可以被Lua代码调用的新函数的函数,等等。所有在lua.h中被定义的都有一个lua_前缀。 头文件lauxlib.h定义了辅助库(auxlib)提供的函数。同样,所有在其中定义的函数等都以luaL_打头(例如,luaL_loadbuffer)。辅助库利用lua.h中提供的基础函数提供了更高层次上的抽象;所有Lua标准库都使用了auxlib。 ? 构建一个最简单的LUA运行环境: ////////////////////构建LUA的基本运行环境 #include <stdio.h> ///使用C调用(不使用的话会有问题) extern "C" {? #include "lua.h" #include "lauxlib.h" #include "lualib.h" } #include <string> int main (void) { ??? char buff[256]; ??? int error; ??? //LUA库的初始化 ??? lua_State *L = lua_open(); /* opens Lua */ ??? luaopen_base(L); /* opens the basic library */ ??? luaopen_table(L); /* opens the table library */ ??? luaopen_io(L); /* opens the I/O library */ ??? luaopen_string(L); /* opens the string lib. */ ??? luaopen_math(L); /* opens the math lib. */ ??? while (fgets(buff,sizeof(buff),stdin) != NULL) ??? { ??????? error = luaL_loadbuffer(L,buff,strlen(buff), ??????????? "line") || lua_pcall(L,0); ??????? if (error) { ??????????? fprintf(stderr,"%s",lua_tostring(L,-1)); ??????????? lua_pop(L,1);/* pop error message from the stack */+ ??????? } ??? } ??? lua_close(L); ??? return 0; } ? LUA堆栈原理: 当在Lua和C之间交换数据时我们面临着两个问题:动态与静态类型系统的不匹配和自动与手动内存管理的不一致。 用一个抽象的栈在Lua与C之间交换值。栈中的每一条记录都可以保存任何Lua值。无论你何时想要从Lua请求一个值(比如一个全局变量的值),调用Lua,被请求的值将会被压入栈。无论你何时想要传递一个值给Lua,首先将这个值压入栈,然后调用Lua(这个值将被弹出)。我们仍然需要一个不同的函数将每种C类型压入栈和一个不同函数从栈上取值(译注:只是取出不是弹出),但是我们避免了组合式的爆炸(combinatorial explosion)。另外,因为栈是由Lua来管理的,垃圾回收器知道那个值正在被C使用。几乎所有的API函数都用到了栈。正如我们在第一个例子中所看到的,luaL_loadbuffer把它的结果留在了栈上(被编译的chunk或一条错误信息);lua_pcall从栈上获取要被调用的函数并把任何临时的错误信息放在这里。 当Lua在起始以及在Lua调用C的时候,栈上至少有20个空闲的记录(lua.h中的LUA_MINSTACK宏定义了这个常量) ? ? LUA基本函数介绍: 压入栈函数: void lua_pushnil (lua_State *L); void lua_pushboolean (lua_State *L,int bool); void lua_pushnumber (lua_State *L,double n); //Lua中的字符串不是以零为结束符的;Lua从来不保持一个指向外部字符串的指针 void lua_pushlstring (lua_State *L,const char *s,size_t length); void lua_pushstring (lua_State *L,const char *s); //同样也有将C函数和userdata值压入栈的函数. int lua_checkstack (lua_State *L,int sz); ? 查询元素 API用索引来访问栈中的元素。在栈中的第一个元素(也就是第一个被压入栈的)有索引1,下一个有索引2,以此类推。 API提供了一套lua_is*函数来检查一个元素是否是一个指定的类型 int lua_is... (lua_State *L,int index); 如:lua_isnumber,lua_isstring,lua_istable 还有一个lua_type函数,它返回栈中元素的类型。(lua_is*中的有些函数实际上是用了这个函数定义的宏)在lua.h头文件中,每种类型都被定义为一个常量:LUA_TNIL、LUA_TBOOLEAN、LUA_TNUMBER、LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA以及LUA_TTHREAD。这个函数主要被用在与一个switch语句联合使用。当我们需要真正的检查字符串和数字类型时它也是有用的. ? ? 为了从栈中获得值,这里有lua_to*函数: int lua_toboolean (lua_State *L,int index); double lua_tonumber (lua_State *L,int index); //Lua_tostring函数返回一个指向字符串的内部拷贝的指针 const char * lua_tostring (lua_State *L,int index); size_t lua_strlen (lua_State *L,int index); ? 其他堆栈操作: 除开上面所提及的C与堆栈交换值的函数外,API也提供了下列函数来完成通常的堆栈维护工作: ??? //返回堆栈中的元素个数,它也是栈顶元素的索引 int lua_gettop (lua_State *L); //lua_settop设置栈顶(也就是堆栈中的元素个数)为一个指定的值,lua_settop(L,0)清空堆栈 void lua_settop (lua_State *L,int index); //压入堆栈上指定索引的一个抟贝到栈顶 void lua_pushvalue (lua_State *L,int index); //移除指定索引位置的元素 void lua_remove (lua_State *L,int index); //移动栈顶元素到指定索引的位置 void lua_insert (lua_State *L,int index); //从栈顶弹出元素值并将其设置到指定索引位置 void lua_replace (lua_State *L,int index); ? //测试代码,返回lua的类型 static void stackDump (lua_State *L) { ??? int i; ??? int top = lua_gettop(L); ??? for (i = 1; i <= top; i++) ?{ /* repeat for each level */ ??????? int t = lua_type(L,i); ??????? switch (t) { case LUA_TSTRING: /* strings */ ??? printf("`%s'",i)); ??? break; case LUA_TBOOLEAN: /* booleans */ ??? printf(lua_toboolean(L,i) ? "true" : "false"); ??? break; case LUA_TNUMBER: /* numbers */ ??? printf("%g",lua_tonumber(L,i)); ??? break; default: /* other values */ ??? printf("%s",lua_typename(L,t)); ??? break; } ??????? printf(" "); /* put a separator */ ??? } ??? printf("n"); /* end the listing */ } ? int main (void) { ??? lua_State *L = lua_open(); ??? lua_pushboolean(L,1); lua_pushnumber(L,10); ??? lua_pushnil(L); lua_pushstring(L,"hello"); ??? stackDump(L); ??? /* true 10 nil `hello' */ ??? lua_pushvalue(L,-4); stackDump(L); ??? /* true 10 nil `hello' true */ ??? lua_replace(L,3); stackDump(L); ??? /* true 10 true `hello' */ ??? lua_settop(L,6); stackDump(L); ??? /* true 10 true `hello' nil nil */ ??? lua_remove(L,-3); stackDump(L); ??? /* true 10 true nil nil */ ??? lua_settop(L,-5); stackDump(L); ??? /* true */ lua_close(L); return 0; } ? 错误处理 Lua中的所有结构都是动态的:它们按需增长,最终当可能时又会缩减。意味着内存分配失败的可能性在Lua中是普遍的。几乎任意操作都会面对这种意外。Lua的API中用异常发出这些错误而不是为每步操作产生错误码。这意味着所有的API函数可能抛出一个错误(也就是调用longjmp)来代替返回。 你可以使用lua_atpanic函数设置你自己的panic函数 不是所有的API函数都会抛出异常,lua_open、lua_close、lua_pcall和lua_load都是安全的 ? ? ? ? ? ? ? ? 下面将介绍LUA中真正会常用到的几个方法(LUA将给程序带来的功能所提到的) 1.LUA可以当一个配置文件使用(读写LUA全局变量) lua配置文件: width=100 height=200 ? 读取配置文件程序: #include <lua.h> #include <lauxlib.h> #include <lualib.h> void load (char *filename,int *width,int *height) { ???? lua_State *L = lua_open(); ???? luaopen_base(L); //?? luaopen_io(L); ???? luaopen_string(L); ???? luaopen_math(L); ???? if (luaL_loadfile(L,filename) || lua_pcall(L,0)) ???? {//读取LUA的配置文件 ???????? printf("cannot run configuration file: %s",-1)); ???? } ???? //得到相应的配置文件内容 ???? lua_getglobal(L,"width"); ???? lua_getglobal(L,"height"); ???? if (!lua_isnumber(L,-2)) ???? { ???????? printf("`width' should be a numbern"); ???? } ???? if (!lua_isnumber(L,-1)) ???? { ???????? printf("`height' should be a numbern"); ???? } ???? *width = (int)lua_tonumber(L,-2); ???? *height = (int)lua_tonumber(L,-1); ???? lua_close(L); } ? ? ? ? ? ? ? ? ? ? ? ? 读取配置文件中的TABLE数据 ? lua 配置内容 : background = {r=0.30,g=0.10,b=0} ? 程序: int GetField(lua_State* L,const char* key) { ???? int result; ???? lua_pushstring(L,key);????????? //把要得到的值压入 ???? lua_gettable(L,-2);???????????? //他接受table在栈中的位置为参数,将对应key值出栈 ???? if (!lua_isnumber(L,-1))??????? ???? { ???????? printf("invalid component in background color"); ???? } ???? result = (int)lua_tonumber(L,-1); ???? lua_pop(L,1); /* remove number */ ???? return result; } ? //从lua中得到table的数据 //RED = {r=255,g=0,b=0} //GRE = {r=0,g=255,b=0} //BLU = {r=0,b=255} void GetTableInfo(char *filename,const char* szTableName) { ???? lua_State *L = lua_open(); ???? luaopen_base(L); ???? luaopen_string(L); ???? luaopen_math(L); ???? if (luaL_loadfile(L,0)) ???? { ???????? printf("cannot run configuration file: %s",-1)); ???? } ???? lua_getglobal(L,szTableName);???????????????????? //得到table的全局变量 ???? if (!lua_istable(L,-1)) ???? { ???????? printf("`width' should be a numbern"); ???? } ???? int Red = GetField(L,"r");?????????????? //得到Table中的r值 ???? int Gre = GetField(L,"g"); ???? int Blu = GetField(L,"b"); ???? printf("%d,%d,%d",Red,Gre,Blu); ???? lua_close(L); } ? ? ? ? ? ? ? ? ? ? ? ? ? 2.程序中调用LUA定义的函数(C函数调用lua) 程序中调用LUA定义的函数 使用API调用函数的方法是很简单的: 首先,将被调用的函数入栈; 第二,依次将所有参数入栈; 第三,使用lua_pcall调用函数; 最后,从栈中获取函数执行返回的结果。 ? lua函数 function f (x,y) return (x^2 * math.sin(y))/(1 - x) end ? //在C中对于给定的x,y计算z=f(x,y)的值 /* call a function `f' defined in Lua */ double f (double x,double y) { double z; /* push functions and arguments */ lua_getglobal(L,"f"); /* function to be called */ lua_pushnumber(L,x); /* push 1st argument */ lua_pushnumber(L,y); /* push 2nd argument */ /* do the call (2 arguments,1 result) */ if (lua_pcall(L,2,1,0) != 0) //。第四个参数可以指定一个错误处理函数 error(L,"error running function `f': %s",-1)); /* retrieve result */ if (!lua_isnumber(L,-1)) error(L,"function `f' must return a number"); z = lua_tonumber(L,-1); lua_pop(L,1); /* pop returned value */ return z; ?????????????????????????????????????????????????????????????????? } ? 在将结果入栈之前,lua_pcall会将栈内的函数和参数移除。如果函数返回多个结果,第一个结果被第一个入栈,因此如果有n个返回结果,第一个返回结果在栈中的位置为-n,最后一个返回结果在栈中的位置为-1。 lua_pcall返回三个LUA_ERRRUN LUA_ERRMEM LUA_ERRER ? 我们的封装后的函数(call_va)接受被调用的函数明作为第一个参数,第二参数是一个描述参数和结果类型的字符串,最后是一个保存返回结果的变量指针的列表。使用这个函数,我们可以将前面的例子改写为: call_va("f","dd>d",x,y,&z); 与lua_pcall有相同的意思 字符串 "dd>d" 表示函数有两个double类型的参数,一个double类型的返回结果。我们使用字母 'd' 表示double;'i' 表示integer,'s' 表示strings;'>' 作为参数和结果的分隔符。如果函数没有返回结果,'>' 是可选的。 ? void call_va (lua_State *L,const char *func,const char *sig,...) { va_list vl; int iParamCnt = 0;//参数个数 int iRetCnt = 0;//返回值什数 va_start(vl,sig); lua_getglobal(L,func); /* get function */ /* push arguments */ bool bRet = false; while (*sig) { /* push arguments */ ???? switch (*sig) ???? { ???? case 'd': /* double argument */ ???????? lua_pushnumber(L,va_arg(vl,double)); ???????? break; ???? case 'i': /* int argument */ ???????? lua_pushnumber(L,int)); ???????? break; ???? case 's': /* string argument */ ???????? lua_pushstring(L,char *)); ???????? break; ???? case '>': ???????? iParamCnt = iParamCnt - 1; ???????? bRet = true; ???????? break; ???? default: ???????? printf("invalid option (%c)",*(sig - 1)); ???? } ???? iParamCnt = iParamCnt + 1; ???? *sig++; ???? if (bRet) ???? { ???????? break; ??? } ???? luaL_checkstack(L,"too many arguments"); }/* endwhile:*/ /* do the call */ iRetCnt = strlen(sig); /* number of expected results */ if (lua_pcall(L,iParamCnt,iRetCnt,0) != 0) /* do the call */ { ???? printf("error running function `%s': %s",func,-1)); } /* retrieve results */ iRetCnt =? - iRetCnt; /* stack index of first result */ while (*sig) { /* get results */ ???? switch (*sig) ???? { ???? case 'd': /* double result */ ???????? {?????? ???????????? if (!lua_isnumber(L,iRetCnt)) ???? ??????? { ???????????????? printf("wrong result type"); ???????????? } ???????????? *va_arg(vl,double *) = lua_tonumber(L,iRetCnt); ???????? } ???????? break; ???? case 'i': /* int result */ ???????? if (!lua_isnumber(L,iRetCnt)) ???????? printf("wrong result type"); ???????? *va_arg(vl,int *) = (int)lua_tonumber(L,iRetCnt); ???????? break; ???? case 's': /* string result */ ???????? if (!lua_isstring(L,const char **) = lua_tostring(L,iRetCnt); ???????? break; ???? default: ???????? printf("invalid option (%c)",*(sig - 1)); ???? } ? ???? iRetCnt++; ???? *sig++; } va_end(vl); } 3.LUA可以调用自定义的C函数 (lua调用C函数) 在本节中我会介绍如何提供C函数给LUA在.lua文件中调用 这儿有一个重要的概念:用来交互的栈不是全局变量,每一个函数都有他自己的私有栈 当Lua调用C函数的时候,第一个参数总是在这个私有栈的index=1的位置 函数注册到LUA中 任何在Lua中注册的函数必须有同样的原型,这个原型声明定义就是lua.h中的lua_CFunction: ? typedef int (*lua_CFunction) (lua_State *L); ? ? static int l_sin (lua_State *L) { double d = lua_tonumber(L,1); /* get argument */ lua_pushnumber(L,sin(d)); /* push result */ return 1; /* number of results */ } ? ? ? 还必须首先注册这个函数。我们使用lua_pushcfunction来完成这个任务 lua_pushcfunction(l,l_sin); //将类型为function的值入栈, lua_setglobal(l,"mysin"); //将function赋值给全局变量mysin static int l_sin (lua_State *L) { double d = luaL_checknumber(L,1); lua_pushnumber(L,sin(d)); return 1; /* number of results */ ???? } ? static int l_dir (lua_State *L) { DIR *dir; struct dirent *entry; int i; const char *path = luaL_checkstring(L,1);//函数用来检测参数是否为字符 /* open directory */ dir = opendir(path); if (dir == NULL) { /* error opening the directory? */ ???? lua_pushnil(L); /* return nil and ... */ ???? lua_pushstring(L,strerror(errno)); /* error message */ ???? return 2; /* number of results */ } /* create result table */ lua_newtable(L); i = 1; while ((entry = readdir(dir)) != NULL) { ???? lua_pushnumber(L,i++); /* push key */ ???? lua_pushstring(L,entry->d_name); /* push value */ ???? lua_settable(L,-3); } closedir(dir); return 1; /* table is already on top */ } 一个Lua库实际上是一个定义了一系列Lua函数的chunk,并将这些函数保存在适当的地方,通常作为table的域来保存Lua的C库就是这样实现的。除了定义C函数之外,还必须定义一个特殊的用来和Lua库的主chunk通信的特殊函数一旦调用,这个函数就会注册库中所有的C函数,并将他们保存到适当的位置。 ? ? ? ? 4.运行LUA文件的函数(调用LUA文件运行函数) lua_State* m_pLuaState = lua_open(); luaL_openlibs(m_pLuaState); if(luaL_dofile(m_pLuaState,strLuaFilePath.c_str())) {//直接运行lua中独立的函数 ???? return false; } return true; 5.运行LUA函数的函数(调用LUA代码片断的函数) 1.???? 选注册必要的C函数(直接调用非C的lua函数也行: 3.LUA可以调用自定义的C函数) 2.???? 把注册的函数写入LUA 3.???? 运行LUA函数的函数 //开放给doluafunc使用的函数,autoluafunc_XXXX char szTemp[256] = {0}; _snprintf_s(szTemp,sizeof(szTemp),"function autoluafunc_%s()n %s();n endn",函数名,函数名); luaL_dostring(m_pLuaState,szTemp);//放入LUA调用表中 ? char szTemp[256] = {0}; _snprintf_s(szTemp,"autoluafunc_%s",函数名); lua_getglobal(m_pLuaState,szTemp); int ret = lua_pcall(m_pLuaState,0); 由以上代码看来运行LUA函数的函数都是无参数的LUA 看上去没有什么用的代码段却在ini配置等方面有很好的应用,如ini中配置一串lua函数,你可以把这一串lua函数用一个特定的无参lua重新定义一遍,然后使用此lua调用,具体的过程还要通过项目实践才会了解~ ? ? ? 以上是我个人总结的一些lua的应用,方便以后开发,如有不明白可以给我留言,没能详尽描述也请原谅. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |