加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

Lua API 小记

发布时间:2020-12-14 22:18:52 所属栏目:大数据 来源:网络整理
导读: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 部分的预分配元素

http://www.cnblogs.com/ringofthec/archive/2010/10/22/lua.html

1.?建一个新表

void?lua_createtable?(lua_State?*L,?int?narr,?int?nrec)

创建一个新的table,?并把它放在栈顶.?narrnrec分别指定该tablearray部分和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))

没有返回值

栈不变化

这个是比较常用的,?nlua中的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_calllua_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.?luac通信的约定

luac通信时有这样的约定:?所有的lua中的值由lua来管理,?c++中产生的值lua不知道,?类似表达了这样一种意思:?"如果你(c/c++)想要什么,?你告诉我(lua),?我来产生,?然后放到栈上,?你只能通过api来操作这个值,?我只管我的世界",?这个很重要,?因为:

"如果你想要什么,?你告诉我,?我来产生"就可以保证,?凡是lua中的变量,?lua要负责这些变量的生命周期和垃圾回收,?必须由lua来创建这些值(在创建时就加入了生命周期管理要用到的簿记信息)

"然后放到栈上,?你只能通过api来操作这个值",?lua?apic提供了一套完备的操作界面,?这个就相当于约定的通信协议,?如果lua客户使用这个操作界面,?那么lua本身不会出现任何"意料之外"的错误.

"我只管我的世界"这句话体现了luac/c++作为两个不同系统的分界,?c/c++中的值,?lua是不知道的,?lua只负责它的世界

3.?lua?value?和?c?value的对应关系

c

lua

nil

{value=0,?tt?=?t_nil}

boolean

int?0,?0

{value=0/0,?tt?=?t_boolean}

number

int/float等?1.5

{value=1.5,?tt?=?t_number}

lightuserdata

void*,?int*,?各种*?point

{value=point,?tt?=?t_lightuserdata}

string

char?str[]

{value=gco,?tt?=?t_string}?gco=TString?obj

table

{value=gco,?tt?=?t_table}?gco=Table?obj

userdata

{value=gco,?tt?=?t_udata}?gco=Udata?obj

closure

{value=gco,?tt?=?t_function}?gco=Closure?obj

可以看出来,?lua中提供的一些类型和c中是对应的,?也提供一些c中没有的类型.?其中有一些药特别的说明一下:

nil,?c中没有对应,?但是可以通过lua_pushnillua中压入一个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_getallocflua_setallocf随时获取和修改它

2.?字符串的hashtable,?lua中所有的字符串都会在该hashtable中注册.

3.?gc相关的信息.?内存使用统计量.

4.?panic,?当无保护调用发生时,?会调用该函数,?默认是null,?可以通过lua_atpanic配置.

5.?注册表,?注册表是一个全局唯一的table.

6.?记录lua中元方法名称?和?基本类型的元表[注意,?luatableuserdata每个实例可以拥有自己的独特的元表--记录在tableuserdatamt字段,?其他类型是每个类型共享一个元表--就是记录在这里].

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_sCallInfostack空间.

4.?其中涉及到了内存分配统统使用lua_newstate传进来的内存分配器分配.

2.?创建新lua执行环境

lua_State?*luaE_newthread?(lua_State?*L)

创建一个新的lua_State,?预分配CallInfostack空间,?并共享l_gt,?虽然每个lua_State都有自己的l_gt,?但是这里是却将新建的lua_Statel_gt都指向主lua_Statel_gt.

注意,?lua_Statelua运行的基础[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栈中.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读