Lua API 小记
http://www.cnblogs.com/ringofthec/archive/2010/10/22/lua.html 1.?建一个新表 void?lua_createtable?(lua_State?*L,?int?narr,?int?nrec) 创建一个新的table,?并把它放在栈顶.?narr和nrec分别指定该table的array部分和hash部分的预分配元素数量 无返回值 栈高度+1,?栈顶元素是新table #define?lua_newtable(L)?lua_createtable(L,?0,?0)?常用这个 2.?取表中的元素 void?lua_getfield?(lua_State?*L,?int?index,?const?char?*k) 操作:???arr?=?Stack[index]????//?arr肯定是表 ????????Stack.push(?arr[k]?) 取表中键为k的元素,?这里的表是由index指向的栈上的一个表 栈顶元素是(Stack[index])[k] 注意,?该操作将触发?__index?元方法 3.?给表中的元素赋值 void?lua_setfield?(lua_State?*L,240)">操作:???arr?=?Stack[index] ????????arr[k]?=?Stack.top() ????????Stack.pop() 给表中键为k的元素赋值value(value就是栈顶元素),240)">栈高度-1,?被弹出的是value 该操作将触发?__newindex?元方法 4.?取表元素?和?表元素赋值 void?lua_gettable?(lua_State?*L,?int?index) 操作:?ele?=?Stack[index] key?=?Stack.top() Stack.pop() value?=?ele[key] Stack.push(value) 根据index指定取到相应的表;?取栈顶元素为key,?并弹出栈;?获取表中key的值压入栈顶. 无返回值 栈高度不变,?但是发生了一次弹出和压入的操作,?弹出的是key,?压入的是value 注意,?该操作将触发?__index?元方法 void?lua_settable?(lua_State?*L,?int?index) 操作:???ele????=?Stack[index] ????????value??=?Stack.top() ????????key????=?Stack.top() ????????ele[key]?=?value 根据index指定取到相应的表;?取栈顶元素做value,?弹出之;?再取当前栈顶元素做key,?亦弹出之;?然后将表的键为key的元素赋值为value 栈高度-2,?第一次弹出value,?第二次弹出key 5.?对table的一些操作[不引发原方法] void?lua_rawget?(lua_State?*L,?int?index) 和lua_gettable操作一样 但是不触发相应的元方法 void?lua_rawgeti(lua_State?*L,?int?n) 操作:?ele?=?Stack[index] ?value?=?ele[n] ?Stack.push(value) 无返回值 栈+1,?栈顶新增元素就是?value 不触发相应的元方法 void?lua_rawset?(lua_State?*L,?int?index) 和lua_settable操作一样 但是不触发相应的原方法 void?lua_rawseti?(lua_State?*L,?int?n) 操作:?ele?=?Stack[index] ?value?=?Stack.top() ?Stack.pop() ?ele[n]?=?value 无返回值 栈-1,?栈顶将value弹出 不触发相应的元方法 6.?复制栈上元素并压入栈 void?lua_pushvalue?(lua_State?*L,?int?index) 操作:?value?=?Stack[index]? ?Stack.push(value) 无返回值 栈+1 7.?创建一个元表 int?luaL_newmetatable?(lua_State?*L,?const?char?*tname) 操作:?1.?在注册表中查找tname,?如果已经注册,?就返回0,?否者继续,?并平栈 ?lua_getfield(L,?LUA_REGISTRYINDEX,?tname) ?if?(!lua_isnil(L,?-1)) ?return?0; ?lua_pop(L,?1); ?2.?创建一个表,?并注册,?返回1 ?lua_newtable(L) ?lua_pushvalue(L,?-1) ?lua_setfield(L,?tname) ?return?1 有返回值 栈+1,?栈顶元素是在注册表中注册过的新表 8.?创建C值 void?*lua_newuserdata?(lua_State?*L,?size_t?size) 该函数分配一块由size指定大小的内存块,?并放在栈顶 返回值是新分配的块的地址 栈+1,?栈顶是userdata userdata用来在lua中表示c中的值.?一个完整的userdata有自己的元表,?在垃圾回收时,?可以调用它的元表的__gc方法 9.?注册c函数到lua中,?其实没有这回事,?lua中只有c闭包 void?lua_pushcclosure?(lua_State?*L,?lua_CFunction?fn,?int?n) 向栈上压一个C闭包 当一个c函数被创建时,?可以绑定几个值在它上面,?从而形成一个闭包.?在任何时刻调用这个c函数时,?都可以访问这几个绑定值. 绑定的方法:?先一次压入要绑定的n个值到栈上,?然后调用lua_pushcclosure(L,?fn,?n)这样就形成的一个c闭包 无返回值 栈?–(n?-?1)?,?一共弹出n个元素(及那些绑定的值),?压入一个cclosure #define?lua_pushcfunction(L,?f)?lua_pushcclosure(L,?f,?0) #define?lua_register(L,?n,?f)?(lua_pushcfunction(L,?f),?lua_setglobal(L,?n)) 没有返回值 栈不变化 这个是比较常用的,?以n为lua中的key压入一个0个绑定值的cclosure. 10.?调用一个lua函数 void?lua_call(lua_State*?L,?int?nargs,?int?nresults) lua?c?api的特点就是"不是一个人在战斗"?[我想表达的意思是,?lua中的一句话,?在c?api实现起来就是n句,?可能有人疑惑那为什么不直接用lua多好,?c?api这么麻烦,?答案是有的事只能用c?api才能实现],?所以,?调用它之前,?需要布局一下栈,?第一,?要把要call的函数压入栈;?第二,?call要用的参数正序压入栈中;?然后才能调用lua_call,?调用完了,?自己去取返回值,?它都给你压栈上了. 操作: argn?=?Stack.pop() ...?//?一共压入nargs个参数 arg2?=?Stack.pop() arg3?=?Stack.pop() func?=?Stack.pop()?//?函数本身也弹出 res1,?res2,?...,?resj?=?func(arg1,?arg2,?...,?argn) Stack.push(res1) Stack.push(res2) …?//?压入nresults个返回值 Stack.push(resj) 无返回值 调用结束后,?栈高度增加?nresults?–?(1?+?nargs),?如果将nresults参数设置为LUA_MULTRET,?那么lua返回几个值,?栈上就压入几个值,?否者强制压入nresults个值,?不足的是空值,?多余的抛弃掉 注意,?这个函数是有危险的,?如果在其中发生了错误,?会直接退出程序 这个函数的用途:?尚未发现,?除非你能接受出错立马退出,?反正我是做游戏的,?我受不起,?呵呵,?顺便一说,?lauxlib.h中的luaL_check*一族函数也是这样的,?不符合预期的话,?直接退出,?这些函数都要小心,?有类似于断言的效果. 11.?保护下调用一个lua函数 int?lua_pcall(lua_State*?L,?int?nresults,?int?errfunc) 参数,?行为和lua_call都一样,?如果在调用中没有发生任何错误,?lua_pcall?==?lua_call;?但是如果有错误发生时,?lua_pcall会捕获它 errfunc指出了Stack上的一个元素,?这个元素应该是一个函数,?当发生错误的时候 ef?=?Stack[errfunc] value?=?ef(errmsg) Stack.push(value) 也就是说,?在错误的时候,?errfunc指定的错误处理函数会被调用,?该处理函数的返回值被压到栈上. 默认情况下,?可以给errfunc传值0,?实际的效果是指定了这样一个函数做出错处理?function?defaulterr(errmsg)?return?errmsg?end. 本函数有返回值?LUA_ERRRUN运行时错误?LUA_ERRMEM内存分配错误[注意,?这种错会导致lua调用不了错误处理函数]?LUA_ERRERR运行错误处理函数时出错了,?写程序的时候必须检查返回值:) 强烈推荐该函数,?不过事实上大家也都用的这个函数:) 12.?保护下调用一个c函数 int?lua_cpcall?(lua_State?*L,?lua_CFunction?func,?void?*ud) 以保护模式调用c函数,?func中可以且只能从堆栈上拿到一个参数,?就是ud,?当有错误时,?和lua_pcall返回相同的错误代码,?并在堆栈顶部留下errmsg字符串,?调用成功的话它返回零,?并且不会修改堆栈,?所有从func中返回的值都被扔掉. 这里注意的问题是: 1.?"当有错误时",?这个错误的意思是lua的错误,?而不是c/c++的错误.?在func中使用lua_call和lua_check*族函数,?并不会导致程序退出了,?而是表现的像lua_pcall那样. 2.?调用成功的时候func中的返回值都被扔掉了. ------------------------------------------------?华丽的分割线?------------------------------------------------------------ 1.?理解lua的栈到底是什么? lua的栈类似于以下的定义,?它是在创建lua_State的时候创建的: TValue?stack[max_stack_len]?//?欲知内情可以查?lstate.c?的stack_init函数 存入栈的数据类型包括数值,?字符串,?指针,?talbe,?闭包等,?下面是一个栈的例子: 执行下面的代码就可以让你的lua栈上呈现图中的情况 lua_pushcclosure(L,?func,?0)?//?创建并压入一个闭包 lua_createtable(L,?0)?//?新建并压入一个表 lua_pushnumber(L,?343)?//?压入一个数字 lua_pushstring(L,?“mystr”)?//?压入一个字符串 这里要说明的是,?你压入的类型有数值,?表和闭包[在c中看来是不同类型的值],?但是最后都是统一用TValue这种数据结构来保存的:),?下面用图简单的说明一下这种数据结构: TValue结构对应于lua中的所有数据类型,?是一个{值,?类型}?结构,?这就lua中动态类型的实现,?它把值和类型绑在一起,?用tt记录value的类型,?value是一个联合结构,?由Value定义,?可以看到这个联合有四个域,?先说明简单的 p?--?可以存一个指针,?实际上是lua中的light?userdata结构 n?--?所有的数值存在这里,?不过是int?,?还是float b?--?Boolean值存在这里,?注意,?lua_pushinteger不是存在这里,?而是存在n中,?b只存布尔 gc?--?其他诸如table,?thread,?closure,?string需要内存管理垃圾回收的类型都存在这里 gc是一个指针,?它可以指向的类型由联合体GCObject定义,?从图中可以看出,?有string,?userdata,?table,?proto,?upvalue,?thread 从下面的图可以的得出如下结论: 1.?lua中,?number,?boolean,?nil,?light?userdata四种类型的值是直接存在栈上元素里的,?和垃圾回收无关. 2.?lua中,?string,?thread存在栈上元素里的只是指针,?他们都会在生命周期结束后被垃圾回收. 2.?lua和c通信的约定 lua和c通信时有这样的约定:?所有的lua中的值由lua来管理,?c++中产生的值lua不知道,?类似表达了这样一种意思:?"如果你(c/c++)想要什么,?你告诉我(lua),?我来产生,?然后放到栈上,?你只能通过api来操作这个值,?我只管我的世界",?这个很重要,?因为: "如果你想要什么,?你告诉我,?我来产生"就可以保证,?凡是lua中的变量,?lua要负责这些变量的生命周期和垃圾回收,?必须由lua来创建这些值(在创建时就加入了生命周期管理要用到的簿记信息) "然后放到栈上,?你只能通过api来操作这个值",?lua?api给c提供了一套完备的操作界面,?这个就相当于约定的通信协议,?如果lua客户使用这个操作界面,?那么lua本身不会出现任何"意料之外"的错误. "我只管我的世界"这句话体现了lua和c/c++作为两个不同系统的分界,?c/c++中的值,?lua是不知道的,?lua只负责它的世界 3.?lua?value?和?c?value的对应关系
可以看出来,?lua中提供的一些类型和c中是对应的,?也提供一些c中没有的类型.?其中有一些药特别的说明一下: nil值,?c中没有对应,?但是可以通过lua_pushnil向lua中压入一个nil值 注意:?lua_push*族函数都有"创建一个类型的值并压入"的语义,?因为lua中所有的变量都是lua中创建并保存的,?对于那些和c中有对应关系的lua类型,?lua会通过api传来的附加参数,?创建出对应类型的lua变量放在栈顶,?对于c中没有对应类型的lua类型,?lua直接创建出对应变量放在栈顶. 例如:?lua_pushstring(L,?“string”)?lua根据"string"创建一个?TString?obj,?绑定到新分配的栈顶元素上 lua_pushcclosure(L,func,?0)?lua根据func创建一个?Closure?obj,?绑定到新分配的栈顶元素上 lua_pushnumber(L,5)?lua直接修改新分配的栈顶元素,?将5赋值到对应的域 lua_createtable(L,?0)lua创建一个Tabke?obj,?绑定到新分配的栈顶元素上 总之,?这是一个?c?value?–>?lua?value的流向,?不管是想把一个简单的5放入lua的世界,?还是创建一个table,?都会导致 1.?栈顶新分配元素?2.?绑定或赋值 还是为了重复一句话,?一个c?value入栈就是进入了lua的世界,?lua会生成一个对应的结构并管理起来,?从此就不再依赖这个c?value lua?value?–>?c?value时,?是通过?lua_to*?族api实现,?很简单,?取出对应的c中的域的值就行了,?只能转化那些c中有对应值的lua?value,?比如table就不能to?c?value,?所以api中夜没有提供?lua_totable这样的接口. 1.?创建lua虚拟机 lua_State?*lua_newstate?(lua_Alloc?f,?void?*ud) 创建一个新的独立的lua虚拟机.?参数指定了内存分配策略及其参数,?让用户可以定制内存分配策略是十分有用的,?比如在游戏服务器端使用lua,?我做过一次统记lua在运行的时候会大量的分配大小小于128字节的内存块,?在这样的环境下,?使用lua原生的分配器就不太适合了,?还好在服务器端,?我们往往已经实现了memory?pool,?这时只需要写一个符合?lua_Alloc?原型的适配器,?然后指定为lua的内存分配器就可以了,?很灵活. 从lua的设计层面来说,?lua只是内存分配器的用户,?它只使用一个简单的接口来分配内存,?而不去实现如何分配,?毕竟内存分配不在lua的功能范围内,?这样使的lua变的更加紧凑,?它只是专注于实现lua本身,?而不需要去关注内存分配策略这样的和lua本身无关的东西.?其实学习lua源代码不光是为了更好的掌握lua,?也是为了学习lua中的体现出来的一些编程思想,?lua是一个高度的一致性的,?优雅的软件作品 失败返回null,?多是因为内存分配失败了 该函数会创建栈 从该函数学习到的东西:?1.?当你制作一个功能时,?最好是理清该功能的核心概念和需求,?然后去实现他们,?功能要模块化,?核心概念之间应该是概念一致的,?联系紧密的[谈何容易,?只能是尽可能的,?随时提醒自己要有这样的想法]. 2.?不要因为功能的实现问题而将一个非该功能核心概念的东西加进来,?反之应该把这些东西抽象化作为用户可配置的形式.[在实现时很容易发生"要用到某个功能了,?就是实现它"这样的情况,?这样并不好]就比如lua,?它的核心概念就是lua虚拟机,?而内存分配只是在实现lua虚拟机的过程中的要用到的一种东西,?但它本身不在lua的核心概念里面,?所以把它暴露出来,?让用户自己去定制. 再说下去就是:?除了系统最核心的功能,?其他的东西能用插件的形式暴露给用户,?使其可配置可扩展. 关于这个函数,?还要做更多的解释,?比如我们看到的lua的绝大多数api的第一个参数都是lua_State*?L,?而这个L就是lua_newstate制造出来的,?那么在分析源码的时候,?当然要去看看lua_newstate到底是干了些什么,?lua_State的结构又是什么,?要了解这些内容,?需要知道lua的内部组织结构,?下面是一张很概括但能反映其结构的图 可以看出来,?在一个独立的lua虚拟机里,?global_State是一个全局的结构,?而lua_State可以有多个 值得说明的是,?当调用lua_newstate的时候,?主要的工作就是1.?创建和初始化global_State?2.?创建一个lua_State,?下面来详细的讲解global_State的内容和作用. global_State 一个lua虚拟机中只有一个,?它管理着lua中全局唯一的信息,?主要是以下功能 1.?内存分配策略及其参数,?在调用lua_newstate的时候配置它们.?也可以通过lua_getallocf和lua_setallocf随时获取和修改它 2.?字符串的hashtable,?lua中所有的字符串都会在该hashtable中注册. 3.?gc相关的信息.?内存使用统计量. 4.?panic,?当无保护调用发生时,?会调用该函数,?默认是null,?可以通过lua_atpanic配置. 5.?注册表,?注册表是一个全局唯一的table. 6.?记录lua中元方法名称?和?基本类型的元表[注意,?lua中table和userdata每个实例可以拥有自己的独特的元表--记录在table和userdata的mt字段,?其他类型是每个类型共享一个元表--就是记录在这里]. 7.?upvalue链表. 8.?主lua_State,?一个lua虚拟机中,?可以有多个lua_State,?lua_newstate会创建出一个lua_State,?并邦定到global_state的主lua_State上. global_State主要是管理lua虚拟机的全局环境. lua_State 1.?要注意的是,?和nil,?table一样,?lua_State也是lua中的一种基本类型,?lua中的表示是TValue?{value?=?lua_State,?tt?=?LUA_TTHREAD} 2.?lua_State的成员和功能 a.?栈的管理,?包括管理整个栈和当前函数使用的栈的情况. b.?CallInfo的管理,?包括管理整个CallInfo数组和当前函数的CallInfo. c.?hook相关的,?包括hookmask,?hookcount,?hook函数等. d.?全局表l_gt,?注意这个变量的命名,?很好的表现了它其实只是在本lua_State范围内是全局唯一的的,?和注册表不同,?注册表是lua虚拟机范围内是全局唯一的. e.?gc的一些管理和当前栈中upvalue的管理. f.?错误处理的支持. 3.?从lua_State的成员可以看出来,?lua_State最主要的功能就是函数调用以及和c的通信. lua_State主要是管理一个lua虚拟机的执行环境,?一个lua虚拟机可以有多个执行环境. lua_newstate函数的流程 经过上面的分析,?可以看出newstate?=?[new?一个?global_state]?+?[new?一个?lua_State],?现在看一下它的流程,?很简单 1.?新建一个global_state和一个lua_State. 2.?初始化,?包括给g_s创建注册表,?g_s中各个类型的元表的默认值全部置为0. 3.?给l_s创建全局表,?预分配l_s的CallInfo和stack空间. 4.?其中涉及到了内存分配统统使用lua_newstate传进来的内存分配器分配. 2.?创建新lua执行环境 lua_State?*luaE_newthread?(lua_State?*L) 创建一个新的lua_State,?预分配CallInfo和stack空间,?并共享l_gt表,?虽然每个lua_State都有自己的l_gt,?但是这里是却将新建的lua_State的l_gt都指向主lua_State的l_gt. 注意,?lua_State是lua运行的基础[CallInfo]和与c通信的基础[stack],?在新的lua_State上操作不会影响到原来的lua_State:),?这个是协程实现的基础.?这里顺便提一下协程,?这里先引一段lua创始人的话:"?我们不信任基于抢占式内存共享的多线程技术.?在?HOPL?论文中,?我们写道:?"我们仍然认为,?如果在连?a=a+1?都没有确定结果的语言中,?无人可以写出正确的程序."?我们可以通过去掉抢占式这一点,?或是不共享内存,?就可以回避这个问题."协程的基础就是"去掉抢占式,?但共享内存",?这里的共享是在lua虚拟机的层面上的,?而不是通常意义上的share?memory,?这里的共享内存直接就指的是不同线程[lua_State]之间,?共享lua_State.l_gt全局表,?全局表可以作为不同协程之间的通信环境,?当然也可以用lua_xmove函数,?协程的事先说到这里. 一个和多lua_State相关的函数是:?在同一个lua虚拟机里传递不同lua_State的值 void?lua_xmove?(lua_State?*from,?lua_State?*to,?int?n) 把from栈上的前n个值弹出,?并压入到to栈中. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |