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, 并把它放在栈顶. 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指向的栈上的一个表

无返回值

栈高度+1, 栈顶元素是(Stack[index])[k]

注意该操作将触发 __index 元方法

3. 给表中的元素赋值

void lua_setfield (lua_State *L, int index, const char *k)

操作:   arr = Stack[index]

        arr[k] = Stack.top()

        Stack.pop()

给表中键为k的元素赋值value(value就是栈顶元素), 这里的表是由index指向的栈上的一个表

无返回值

栈高度-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()

        Stack.pop()

        key    = Stack.top()

        Stack.pop()

        ele[key] = 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 = 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 index, 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, LUA_REGISTRYINDEX, 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 nargs, 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, 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. 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, 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, string, 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栈中.

你可能感兴趣的:(Lua API 小记)