Lua API 小记.docx

上传人:丁** 文档编号:1616661 上传时间:2019-10-30 格式:DOCX 页数:11 大小:114.75KB
返回 下载 相关 举报
Lua API 小记.docx_第1页
第1页 / 共11页
Lua API 小记.docx_第2页
第2页 / 共11页
Lua API 小记.docx_第3页
第3页 / 共11页
点击查看更多>>
资源描述
Lua API 小记转载自RingOfTheCring.of.the.cgmail.com一、第一部分(API)这些东西是平时遇到的, 觉得有一定的价值, 所以记录下来, 以后遇到类似的问题可以查阅, 同时分享出来也能方便需要的人, 转载请注明来自RingOfTheCring.of.the.cgmail.com打算记录一些lua_api, 可能会觉得lua文档中已经说的很清楚了, 但是我将用自己的方式, 记录下我认为重要的东西, 先约定一下api说明的格式 编号. api作用简述 api函数原型 api操作说明 返回值说明 对栈的影响 注意事项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 = Stackindex / arr肯定是表 Stack.push( arrk )取表中键为k的元素, 这里的表是由index指向的栈上的一个表无返回值栈高度+1, 栈顶元素是(Stackindex)k注意, 该操作将触发 _index 元方法3. 给表中的元素赋值void lua_setfield (lua_State *L, int index, const char *k)操作: arr = Stackindex arrk = Stack.top() Stack.pop()给表中键为k的元素赋值value(value就是栈顶元素), 这里的表是由index指向的栈上的一个表无返回值栈高度-1, 被弹出的是value注意, 该操作将触发 _newindex 元方法4. 取表元素 和 表元素赋值void lua_gettable (lua_State *L, int index)操作: ele = Stackindex key = Stack.top() Stack.pop() value = elekey Stack.push(value)根据index指定取到相应的表; 取栈顶元素为key, 并弹出栈; 获取表中key的值压入栈顶.无返回值栈高度不变, 但是发生了一次弹出和压入的操作, 弹出的是key, 压入的是value注意, 该操作将触发 _index 元方法void lua_settable (lua_State *L, int index)操作: ele = Stackindex value = Stack.top() Stack.pop() key = Stack.top() Stack.pop() elekey = value根据index指定取到相应的表; 取栈顶元素做value, 弹出之; 再取当前栈顶元素做key, 亦弹出之; 然后将表的键为key的元素赋值为value无返回值栈高度-2, 第一次弹出value, 第二次弹出key注意, 该操作将触发 _newindex 元方法5. 对table的一些操作不引发原方法void lua_rawget (lua_State *L, int index)和lua_gettable操作一样但是不触发相应的元方法void lua_rawgeti(lua_State *L, int index, int n)操作: ele = Stackindex value = elen Stack.push(value)无返回值栈+1, 栈顶新增元素就是 value不触发相应的元方法void lua_rawset (lua_State *L, int index) 和lua_settable操作一样但是不触发相应的原方法void lua_rawseti (lua_State *L, int index, int n) 操作: ele = Stackindex value = Stack.top() Stack.pop() elen = value无返回值栈-1, 栈顶将value弹出不触发相应的元方法6. 复制栈上元素并压入栈void lua_pushvalue (lua_State *L, int index)操作: value = Stackindex 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, LUA_REGISTRYINDEX, tname) return 1有返回值栈+1, 栈顶元素是在注册表中注册过的新表8. 创建C值void *lua_newuserdata (lua_State *L, size_t size)该函数分配一块由size指定大小的内存块, 并放在栈顶返回值是新分配的块的地址栈+1, 栈顶是userdatauserdata用来在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 nargs, int nresults, int errfunc)参数, 行为和lua_call都一样, 如果在调用中没有发生任何错误, lua_pcall = lua_call; 但是如果有错误发生时, lua_pcall会捕获它errfunc指出了Stack上的一个元素, 这个元素应该是一个函数, 当发生错误的时候 ef = Stackerrfunc 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中的返回值都被扔掉了.二、第二部分(lua值, 栈解释)1. 理解lua的栈到底是什么? lua的栈类似于以下的定义, 它是在创建lua_State的时候创建的: TValue stackmax_stack_len / 欲知内情可以查 lstate.c 的stack_init函数 存入栈的数据类型包括数值, 字符串, 指针, talbe, 闭包等, 下面是一个栈的例子: 执行下面的代码就可以让你的lua栈上呈现图中的情况 lua_pushcclosure(L, func, 0) / 创建并压入一个闭包 lua_createtable(L, 0, 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, closure, table, proto, upvalue, thread 从下面的图可以的得出如下结论: 1. lua中, number, boolean, nil, light userdata四种类型的值是直接存在栈上元素里的, 和垃圾回收无关. 2. lua中, string, table, closure, userdata, 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, 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这样的接口.三、第三部分 (lua虚拟机初始化)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_State1) 要注意的是, 和nil, string, table一样, lua_State也是lua中的一种基本类型, lua中的表示是TValue value = lua_State, tt = LUA_TTHREAD2) 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栈中.
展开阅读全文
相关资源
相关搜索

当前位置:首页 > 管理文书 > 各类标准


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!