lua5.3程序设计进阶

声明:本篇博客主要对lua和c交互时,一些比较重要且有意思的特性进行阐述。如果想要了解怎样搭建交互环境,可以参考lua5.3与c交互环境。如果想要了解博客中提到的lua c api详细信息,可以参考官方英文文档或者翻译中文文档

1.lua中常见的c文件如下:
1>.lua.h中定义LUA_开头的基础宏和lua_开头的基础函数(如:操作lua全局变量,访问lua函数,给lua注册函数等),主要注重简洁和高性能。
2>.lauxlib.h中定义luaL_开头的辅助函数(对lua.h中定义的基础函数进行上层封装),主要注重实用。
3>.lualib.h中定义luaopen_开头的标准库函数。由于lua虚拟机创建时不包含任何接口,所以可以使用luaL_openlibs来将预定义标准库注入到lua虚拟机中,也可以使用luaopen_标准库名(如:base,coroutine,debug,io,math,os,package,string,table,utf8)来将指定标准库注入到lua虚拟机中,此时就可以在lua中使用注入进来的标准库所关联的lua api了。
4>.luaconf.h中定义配置。如:LUA_NUMBER是double/long/int/long double等;LUA_INTEGER是int/long/long long等。

2.lua是一种可扩展性的嵌入式语言。具有以下特性:
1>.lua c api是一个函数,常量和类型组成的集合,lua中的所有功能都可以通过lua c api来完成。由于lua c api大多数函数不会检查参数的正确性,所以我们可以在调试c代码的时候使用宏LUA_USE_APICHECK来进行检查。
2>.可扩展性指的是可以使用c编写扩展库,然后以lua c api的形式注入到lua虚拟机中。
3>.嵌入式指的是可以在c中将lua作为库文件,然后以lua c api的形式进行调用。

3.lua和c之间使用栈进行交互,具有以下特性:
1>.解决了lua中动态数据类型和c中静态数据类型之间匹配的问题。
2>.解决了lua中自动内存回收和c中手动内存回收造成内存泄漏的问题。
3>.使用后进先出规则来操作栈,最后压栈的元素最先弹出。
4>.从栈底到栈顶方向的元素索引以1开始依次+1;从栈顶到栈底方向的索引以-1开始依次-1;默认栈上可以使用LUA_MINSTACK(lua.h中定义,值为20)个索引。

4.c调用lua操作时,具有以下特性:
1>.luaL_loadfile函数用来加载指定路径的lua文件,然后将该lua文件编译成代码块但不运行。加载成功时返回0并且将代码块封装成lua函数压入到栈顶;否则返回非0值并将错误信息压入栈顶。
2>.lua_pcall(nparam, nresult, nerror)函数会以保护模式调用栈中函数。流程如下:
1>>.参数nparam表示被调函数所需要参数的个数。在压入被调函数后,需要紧接着压入nparam个参数。
2>>.参数nresult表示被调函数返回结果的个数。在被调函数返回结果不足nresult部分就填nil,大于nresult部分就舍弃。
3>>.nerror表示异常处理函数在栈中索引位置。为0时表示没有异常处理函数;否则表示有异常处理函数,且该函数应该位于被调函数下面。
4>>.当被调函数调用结束后会将被调函数以及参数从栈中弹出。运行成功时返回LUA_OK状态码以及将nresult个返回值压入到栈中;运行失败时返回异常错误状态码以及将异常错误信息(LUA_ERRMEM或者LUA_ERRERR或者没有异常处理函数时就是被调函数自身返回的异常信息;否则就是异常处理函数处理后的异常信息)压入到栈中。
3>.lua_getglobal(var)函数用来将lua代码块中的全局变量var的值压入栈,并返回该值的类型(LUA_TBOOLEAN,LUA_TNUMBER,LUA_TTABLE)。
4>.lua_setglobal(var)函数会将栈顶元素值弹出,然后将lua代码块中的全局变量var的值设置为栈顶元素值。
5>.lua_createtable(narr, nrec)函数用来创建一个空表,然后将这个空表压入栈中。其中该空表的整数索引部分预分配narr个;该空表的哈希索引部分预分配nrec个。lua_newtable等价于lua_createtable(0, 0)。
6>.lua_gettable (idx)函数用来获取指定索引位置idx处的表,然后将栈顶的键弹出并且获取表中该键的值,最后将该值压入栈顶并返回该值类型。
7>.lua_getfield(idx, key)函数用来获取指定索引位置idx处的表,然后将表中指定键key的值压入栈顶并返回该值得类型。
8>.lua_settable(idx)函数用来获取指定索引位置idx处的表,然后将栈顶的值和栈顶下一位置的键组成键值对一起弹出,然后用该键值对修改表。
9>.lua_setfield(idx, key)函数用来获取指定索引位置idx处的表,然后将栈顶的值弹出并将该值和指定键key组成键值对,然后用该键值对修改表。
10>.c中调用lua函数时基本流程为:根据lua_pcall函数中是否指定异常信息处理函数索引来决定是否通过lua_pushcfunction函数来压入异常信息处理函数 -> 通过lua_getglobal函数来将lua代码块中的全局函数压入栈中 -> 根据lua_pcall函数中参数个数来决定是否通过lua_push*(*可以为number,string,integer等)函数来压入参数值 -> 使用lua_pcall函数调用压入栈中的lua函数 -> 当运行成功时返回LUA_OK状态码以及从栈中取出lua_pcall函数中指定的返回值数量个结果;当运行失败时返回异常错误状态码以及从栈中取出异常信息。
11>.c中调用lua函数时通用代码为:

// func:lua代码块中的函数名
// sig:描述参数类型和结果类型的字符串。其中>左边表示参数类型,右边表示返回结果类型
// ...:参数列表以及存放结果的指针
void call_va(lua_State* L, const char* func, const char* sig, ...)
{
	va_list vl;
	va_start(vl, sig);
	
	// 压入lua函数到栈中
	lua_getglobal(L, func);
	// 循环压入参数列表到栈中
	int narg = 0;
	for (narg = 0; *sig; narg++)
	{
		// 扩展空间到top+1
		luaL_checkstack(L, 1, "too many arguments");

		switch (*sig++)
		{
		case 'd':
			lua_pushnumber(L, va_arg(vl, double));
			break;
		case 'i':
			lua_pushinteger(L, va_arg(vl, int));
			break;
		case 's':
			lua_pushstring(L, va_arg(vl, char*));
			break;
		case '>':
			goto endargs; // 从循环中跳出
		default:
			error(L, "invalid option (%c)", *(sig - 1));
			break;
		}
	}
	endargs: {}

	// 获取返回结果个数
	int nres = strlen(sig);
	// 调用lua函数
	if (lua_pcall(L, narg, nres, 0) != LUA_OK)
	{
		error(L, "error calling '%s':%s", func, lua_tostring(L, -1));
	}
	// 循环接收返回结果
	nres = -nres;
	while (*sig)
	{
		switch (*sig++)
		{
		case 'd': {
			int isnum;
			double n = lua_tonumberx(L, nres, &isnum);
			if (!isnum)
			{
				error(L, "error result type");
			}
			*va_arg(vl, double*) = n;
			break;
		}
		case 'i': {
			int isnum;
			int n = lua_tointegerx(L, nres, &isnum);
			if (!isnum)
			{
				error(L, "error result type");
			}
			*va_arg(vl, int*) = n;
			break;
		}
		case 's': {
			const char* n = lua_tostring(L, nres);
			if (n == NULL)
			{
				error(L, "error result type");
			}
			*va_arg(vl, const char**) = n;
			break;
		}
		default:
			error(L, "invalid option (%c)\n", *(sig - 1));
			break;
		}

		nres++;
	}

	va_end(vl);
}

5.lua调用c操作时,具有以下特性:
1>.lua调用c函数的原型为lua_CFunction。宏定义如下:

/*
** Type for C functions registered with Lua
*/
typedef int (*lua_CFunction) (lua_State *L);

其中参数值为lua虚拟机对象;返回值为一个整型值,代表函数返回值的个数。
2>.lua调用c函数有2种方式。如下所示:
1>>.使用lua_pushcfunction函数将lua_CFunction压入栈顶,然后使用lua_setglobal(var)函数来将栈顶的lua_CFunction赋值给lua中的全局变量var。这样就可以在lua中以全局变量var的形式来调用lua_CFunction。
2>>.在lua中以"模块名.lua函数名"的形式来调用lua函数名所关联的lua_CFunction。流程如下:
1>>>.新建一个c模块文件。该文件名没有限制,但是一般都是"l+模块名+lib"的形式命名。
2>>>.在c模块文件中定义要被lua调用的static lua_CFunction。该函数名没有限制,但是一般都是"模块名_lua函数名"的形式命名。
3>>>.在c模块文件中定义一个static const struct luaL_Reg数组变量。该数组变量名没有限制,但是一般都是"模块名lib"的形式命名。其中luaL_Reg的定义如下所示:

typedef struct luaL_Reg {
  const char *name;
  lua_CFunction func;
} luaL_Reg;

name表示lua函数名,func表示c函数地址。数组最后一行需要加入一个结束哨兵{NULL, NULL}。
4>>>.在c模块文件中定义一个LUAMOD_API lua_CFunction作为打开函数。该函数名一般都是"luaopen_模块名"的形式命名。该函数内部主要是用luaL_newlib函数创建一个新的表,然后用记录lua函数名和lua_CFunction关系的luaL_Reg数组变量去填充该表。
5>>>.如果将c模块文件编译成静态库或者动态库的话,就要将生成的库文件放入到lua的库查找路径中。然后lua端在调用"require 模块名"时会从库路径中查找"模块名"对应的库文件。找到库文件后就调用库文件中的"luaopen_模块名"打开方法来将lua函数名和lua_CFunction以表的形式赋值给"模块名"全局变量。此时就可以通过模块名调用lua函数名关联的lua_CFunction。
6>>>.如果将c模块文件和lua解释器(一般为lua.c文件)一起编译的话,就要在lualib.h中定义模块名的宏和声明c模块的打开函数,模块名的宏命名没有限制,但是一般都是"LUA_模块名NAME"的形式命名。然后将模块名的宏和c模块的打开函数作为条目加入到linit.c文件中的loadedlibs数组中。最后在调用luaL_openlibs函数解析loadedlibs数组时,该函数内部会使用luaL_requiref函数将lua函数名和lua_CFunction以表的形式赋值给模块名全局变量。此时就可以通过模块名调用lua函数名关联的lua_CFunction。
3>.lua每次调用c函数时都会开辟一个私有局部栈。此时c函数中常见的操作如下:
1>>.lua传递给c函数的参数值列表会从栈底依次向上压入到栈中。此时可以使用luaL_check*(*可以为number,integer等)函数来检查栈中指定索引位置的参数值是否是指定类型*,如果不是就会抛出异常,否则就会返回该参数值。
2>>.c函数传递给lua的返回值列表会依次使用lua_push*(*可以为number,integer等)函数来向栈顶压入返回值。
3>>.c函数返回值个数为0的话,lua端就直接清空栈;否则lua端会从栈顶开始依次向下获取指定返回值个数的元素值,然后将获取的元素值按照逆序的方式依次赋值给lua端的接收变量,最后清空栈。
4>.lua协程的resume和yiled状态之间存在调用c函数时,由于c函数的调用信息会被丢失从而导致c函数调用异常报错。此时可以使用延续函数结合lua_pcallk函数来解决这个问题,延续函数的定义如下:

/*
** Type for continuation functions
*/
typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx);

其中第一个参数表示lua虚拟机,第二个参数表示栈顶函数调用的状态码,第三个参数表示上下文整形值。返回值为一个整型值,代表函数返回值的个数。
5>.lua_pcallk(nparam, nresult, error, context, kfunc)函数会以保护模式调用栈顶函数。当栈顶函数不需要yield时,lua_pcallk的行为和lua_pcall完全一致;否则就会将状态码(此时为LUA_YIELD或者异常错误码)以及上下文context作为参数来调用延续函数kfunc。

6.lua中的内存申请和内存回收都是通过指定的分配函数实现的。具有以下特性:
1>.分配函数的原型为lua.h文件中定义的lua_Alloc宏。
定义形式如下所示:

/*
** Type for memory-allocation functions
*/
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);

参数说明如下所示:
.ud表示用户数据。
.ptr表示申请或者回收内存块的地址。
.osize表示原始内存块的大小。当ptr不为NULL时,osize就是ptr指向内存块的大小;否则osize就是用来存放一些调试信息,如:表示创建对象的类型(lua.h中定义的base type类型,如:LUA_TSTRING,LUA_TTABLE,LUA_TFUNCTION, LUA_TUSERDATA,LUA_TTHREAD)。
.nsize表示申请内存块的大小。当nsize为0时,如果ptr为NULL就直接返回NULL;否则就会回收ptr指向内存块的地址并返回NULL。当nsize不为0时,如果ptr为NULL就可以直接申请nsize大小的新内存块并返回该新内存块地址;否则就会对ptr指向内存块进行重新分配并返回新的内存块的地址。如果申请内存块失败就返回NULL。
2>.lua_getallocf(ud)函数可以用来获取lua虚拟机上的内存分配函数。当ud不为NULL时就会将内存分配函数中的用户数据填充到ud上。
3>.lua_setallocf(ud)函数可以用来设置lua虚拟机上的内存分配函数,并且该内存分配函数的用户数据为ud。
4>.lauxlib.h中提供的luaL_newstate函数在创建lua虚拟机的同时调用了默认内存分配函数l_alloc,且该函数实现代码如下:

// 由ISO C标准会托管free(NULL)以及realloc(NULL, nsize)等价于malloc(nsize)的正确性。
static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
  (void)ud; (void)osize;  /* not used */
  if (nsize == 0) {
    free(ptr);
    return NULL;
  }
  else
    return realloc(ptr, nsize);
}

5>.lua.h中提供的lua_newstate函数在创建lua虚拟机时需要自己指定lua_Alloc内存分配函数。且一个优秀的内存分配函数具有以下特性:
1>>.能够提供常规的内存申请和回收操作。
2>>.新的分配函数必须能够对旧的分配函数申请的内存进行回收。
3>>.缓存空余内存以达到重用的目的。
4>>.多线程环境下,可以让每个lua状态从私有的内存池中申请内存,从而避免在分配函数中每个lua状态为了线程同步而造成的额外开销。

7.栈和c进行数据交换时,常用的lua c api如下:
1>.lua_checkstack(n)函数确保栈上至少有n个额外空位,如果空位不够就尝试扩大到指定n个空位,最终空位还是不够时就返回假,否则就返回真。
2>.lua_push*函数用来向栈中压入指定*(可以为boolean,number,thread等lua基础数据类型)类型值,此时必须确保栈上有足够的空间。
3>.lua_type函数用来获取指定索引位置元素的类型。
4>.lua_typename函数用来获取指定类型的名称。
5>.lua_is*函数用来判定指定索引位置元素是否为指定*(可以为boolean,number,thread等lua基础数据类型)类型。
6>.lua_to*函数用来将指定索引位置元素转换为指定*(可以为boolean,number,thread等lua基础数据类型)类型值。

8.栈和c进行通用操作时,常用的lua c api如下:
1>.lua_gettop函数用来获取栈顶元素索引值,也就是获取栈中元素总个数。
2>.lua_settop函数用来设置栈顶元素索引值,其中大于新栈顶元素索引值的部分会被清空回收掉,小于新栈顶元素索引值部分会填充nil。
3>.lua_pop(n)函数用来从栈顶弹出n个元素,等价于lua_setpop(L, -(n) - 1)。
4>.lua_pushvalue函数用来将指定索引位置处的元素副本压入栈顶。
5>.lua_replace函数用来将栈顶元素替换指定索引位置处的元素,然后将栈顶元素弹出。
6>.lua_copy(fromidx, toidx)函数用来将指定fromidx索引位置处的元素替换另一指定toidx索引位置处的元素。
7>.lua_rotate(idx, n)函数用来旋转栈中元素。当n为正值时就从栈顶元素索引位置开始,向下找到n个元素,并将这n个元素插入到指定idx索引位置处,原先idx索引位置以及剩余元素全部上移一位。当n为负值时就从idx索引位置开始向上找|n|个元素,然后以找到元素索引位置开始到栈顶元素索引位置结束的所有元素插入到指定idx索引位置处,原先idx索引位置以及剩余元素全部上移一位。
8>.lua_remove(idx)函数用来删除指定索引位置处的元素,并将该位置以上的元素下移一位来填充。等价于lua_rotate(L, idx, -1) lua_pop(1)。
9>.lua_insert(idx)函数用来将栈顶元素旋转到指定索引位置处,原先idx索引位置以及剩余元素全部上移一位,等价于lua_rotate(L, idx, 1)。
10>.lua_error函数会以栈顶元素作为错误对象,抛出一个lua错误。
11>.lua_atpanic函数会设置一个新的紧急处理函数并返回旧的紧急处理函数。当出现异常退出时,会调用这个新的紧急处理函数,处理完后可以用旧的紧急处理函数来还原设置。
12>.lua_call函数会以不安全模式调用栈顶函数。当被调用栈顶函数发生错误时,错误信息通过 longjmp一直上抛并最终调用紧急处理函数后退出程序。
13>.luaL_optinteger(arg, default)函数用来检查正在运行的函数中第arg个参数值。具有以下特性:
1>>.如果参数值不存在或者为nil,此时就返回默认值default。
2>>.如果参数值可以转换成数值类型就返回转换后的数值;否则就抛出异常。
14>.luaL_argerror(arg, extramsg)函数用来抛出一个错误。错误信息中包含c函数的第arg个参数的问题以及额外说明信息extramsg。
15>.luaL_argcheck(cond, arg, extramsg)函数用来检查条件cond是否为真,如果为真时就检查通过,否则就以arg和extramsg为参数调用luaL_argerror函数来抛出一个异常。
16>.lua_isnone(index)函数用来判定指定的索引index位置是否有效。有效时返回1,否则返回0。
17>.luaL_checkany(arg)函数用来检查正在运行的函数中是否含有第arg个参数。如果有就检查通过;否则就抛出异常。
18>.luaL_optlstring(arg, default, length)函数用来检查正在运行的函数中第arg个参数值。具有以下特性:
1>>.如果参数值不存在或者为nil,此时就返回默认值default。
2>>.如果参数值可以转换成字符串类型就返回转换后的字符串;否则就抛出异常。
3>>.如果函数没有抛出异常,此时就会将返回字符串的长度写入length中。
19>.lua_xmove(from, to, n)函数用来从from的栈顶开始往下弹出n个元素,然后按照逆序的方式将弹出的元素依次压入to的栈中。
20>.luaL_requiref(modname, openf, glb)函数通常用在c端中引用模块,其行为类似与在lua端使用require引用模块。首先从package.loaded表中查看是否存在键为modname对应的有效值(不为nil或者false),如果不存在就会使用modname作为key,openf函数返回值作为value存储在package.loaded表中;然后当glb为真(非0)时就会将openf函数返回结果存储在全局变量modname里。

9.对数组(lua中索引为整形值的表)进行操作时,常见的lua c api如下所示:
1>.luaL_checktype(index, type)函数用来检测指定索引位置index处的元素是否是指定类型type。如果不是就抛出异常。
2>.lua_len(index)函数使用#操作符调用length元方法来获取指定索引位置index处的元素的长度。该长度可以是任意类型,然后将该长度压入栈中。
3>.luaL_len(index)函数使用#操作符调用length元方法来获取指定索引位置index处的元素的长度。如果该长度不是整形就抛出异常,否则就返回该长度。
4>.lua_geti(index, key)函数用来获取指定索引位置index处的表中指定索引位置key处的值。然后将该值压入栈中并返回将该值的类型。
5>.lua_rawgeti(index, key)函数的用途跟lua_geti一样。其中lua_geti会触发__index元方法,而lua_rawgeti不会触发__index元方法。所以在没有元表存在时,lua_rawgeti的性能高于lua_geti。
6>.lua_seti(index, key)函数用来将指定索引位置index处的表中指定索引位置key处的值设置成栈顶的元素值。然后将栈顶的元素值弹出。
7>.lua_rawseti(index, key)函数的用途跟lua_seti一样。其中lua_seti会触发__newindex元方法,而lua_rawseti不会触发__newindex元方法。所以在没有元表存在时,lua_rawseti的性能高于lua_seti。

10.对字符串进行操作时,常见的lua c api如下所示:
1>.lua_pushlstring(s, length)函数将字符串s从头开始截取长度length的子串副本压入栈顶。然后返回该子串副本地址。
2>.lua_pushstring(s)函数将以\0结尾的字符串s副本压入栈顶。然后返回该字符串副本地址。
3>.lua_concat(n)函数会从栈顶开始向下弹出n个元素。然后将这n个元素使用lua中的"…"操作符拼接成一个新字符串。最后将这个新字符串压入栈中。
4>.lua_pushfstring(format, …)函数用来将…参数列表按照指定format(只有%s,%d,%f,%p,%I,%c,%U以及%%)格式化为一个新的字符串。然后将该字符串压栈并返回地址。
5>.luaL_checklstring(index, length)函数用来查看指定index索引位置处的元素是否是字符串类型。如果不是就抛出异常;否则就返回字符串长度(length接收)和地址(函数返回值接收)。
6>.luaL_buffinit(buff)函数用来创建一个没有长度的缓冲区buff。其中buff的原型如下:

typedef struct luaL_Buffer {
  char *b;  /* buffer address */
  size_t size;  /* buffer size */
  size_t n;  /* number of characters in buffer */
  lua_State *L;
  char initb[LUAL_BUFFERSIZE];  /* initial buffer */
} luaL_Buffer;

7>.luaL_prepbuffsize(buff, length)函数用来对缓冲区buff分配指定长度length,然后返回指向该缓冲区的地址。
8>.luaL_buffinitsize(buff, length)函数用来创建指定长度length的缓冲区buff,然后返回指向该缓冲区的地址。等价于luaL_buffinit+luaL_prepbuffsize。
9>.luaL_addvalue(buff)函数用来将栈顶的字符串弹出并加入到缓冲区buff中。
10>.luaL_addlstring(buff, str, length)函数用来向缓冲区buff中添加指定长度length的字符串str。
11>.luaL_addstring(buff, str)函数用来向缓冲区buff中添加以\0结尾的字符串str。
12>.luaL_addchar(buff, c)函数用来向缓冲区buff中添加一个字符c。
13>.luaL_pushresult(buff)函数用来将缓冲区buff中的字符串生成一个lua的字符串并压入栈中。
14>.luaL_pushresultsize(buff, length)函数用来将缓冲区buff中指定长度length的字符串生成一个lua的字符串并压入栈中。

11.注册表是位于伪索引LUA_REGISTRYINDEX处的一张全局表。具有以下特性:
1>.注册表用来保存所有模块之间的共享数据。所以在选择键值时,我们可以将库名作为键值的前缀或者使用引用值作为键值或者使用静态变量地址作为键值来尽量避免其他模块重写键值造成数据覆盖等问题。
2>.注册表中数值类型的键值被用作引用系统。lua虚拟机创建时,在注册表中默认分配两个预定义的引用。一个是表示lua虚拟机本身的主线程LUA_RIDX_MAINTHREAD;另一个表示指向全局变量的LUA_RIDX_GLOBALS。
3>.在注册表中使用引用系统来保存lua端非局部数据时,常见的lua c api如下所示:
1>>.luaL_ref(index)函数用来将栈顶的元素弹出并为其分配一个唯一的引用值(nil的引用值为LUA_REFNIL;无效引用值为LUA_NOREF),然后使用该引用值和元素作为键值对来填充指定索引位置index处的表。
2>>.luaL_unref(index, ref)函数用来将指定索引位置index处的表中指定引用值ref的条目移除掉。此时引用对象可以参与垃圾回收,引用值ref也可以被回收再次利用。
3>>.lua_rawgeti(index, ref)函数用来获取指定索引位置index处的表中指定引用值ref关联的数据。
4>.在注册表中使用静态变量地址来保存lua端非局部数据时,常见的lua c api如下所示:
1>>.lua_rawsetp(index, p)函数用来将栈顶元素弹出作为value,将指定静态变量地址p作为key。然后用该键值对填充指定索引位置index处的表。
2>>.lua_rawgetp(index, p)函数用来获取指定索引位置index处的表中指定静态变量地址p作为key时的值。然后将该值压入栈中并返回其类型。

12.闭包是c函数与上值进行的一种关联。具有以下特性:
1>.上值跟c中的静态变量类似,都是只能在关联的c函数中进行访问。当c函数被回收时,关联的上值也将被回收。
2>.使用上值来保存lua端非局部数据时,常见的lua c api如下所示:
1>>.lua_pushcclosure(func, num)函数从栈顶开始往下弹出num个元素作为上值,然后将c函数func压入栈中并与弹出的上值形成闭包。具有以下特性:
1>>>.num最大值不能大于255。
2>>>.最后弹出的元素作为第一个上值,最先弹出的元素作为最后一个上值。
2>>.lua_upvalueindex(index)函数用来返回正在运行函数中第index个上值的伪索引。具有以下特性:
1>>>.index最大值不能大于256。
2>>>.第index个上值不存在时就返回伪索引LUA_TNONE;否则就可以通过该伪索引对关联的上值进行操作。
3>.使用上值在库的所有函数之间共享数据时,常见的lua c api如下所示:
1>>.luaL_setfuncs(lib, num)函数会先从栈顶开始往下弹出num个元素作为上值,然后将lib中的函数添加到栈顶新表中,最后将弹出来的上值作为栈顶新表的共享上值。具有以下特性:
1>>>.num最大值不能大于255。
2>>>.最后弹出的元素作为第一个上值,最先弹出的元素作为最后一个上值。

13.用户数据是lua提供的一种基础数据类型。具有以下特性:
1>.用户数据是一块没有任何预操作的原始内存区域,可以用来存储任何类型数据。
2>.由于用户数据自身是没有任何属性的,所以可以使用元表来扩展用户数据的功能。元表中常用的元方法如下:
1>>.__index元方法在访问用户数据属性名时被调用。
2>>.__newindex元方法在设置用户数据属性值时被调用。
3>>.__len元方法在使用#获取用户数据长度时被调用。
4>>.__tostring元方法在将用户数据转换成字符串时被调用。
5>>.__gc元方法在用户数据被回收时调用,通常在里面回收系统管理的一些资源(如文件指针,窗口句柄等)。
3>.对用户数据进行操作时,常见的lua c api如下所示:
1>>.lua_newuserdata(size)函数用来创建一个指定size大小内存区域,然后将该内存地址压入栈中并返回。用户可以通过接收该内存地址来进行操作。
2>>.lua_touserdata(index)函数用来将指定index位置处的元素转换成用户数据类型。如果转换失败就抛出异常;否则就返回该用户数据地址。
3>>.luaL_newmetatable(tname)函数用来创建一张键值对为__name=tname的元表。当注册表中存在键tname值时就返回0;否则就将元表作为值,tname作为键的形式存储在注册表中并返回1。
4>>.luaL_getmetatable(tname)函数用来将注册表中指定键tname对应的元表压栈并返回该元表类型。如果注册表中没有tname对应的元表时就将nil压栈并返回LUA_TNIL。
5>>.lua_setmetatable(index)函数用来将栈顶的元素弹出并设置成指定索引位置index处的值的元表。
6>>.luaL_checkudata(arg, tname)函数用来检查函数中第arg个参数是否为元表名为tname的用户数据。如果不是就抛出异常;否则就返回该用户数据地址。
7>>.lua_pushlightuserdata(*p)函数用来将轻量用户数据(也就是指针p指向数据的地址)压入栈中。当轻量用户数据判定相等时就表明指向数据的地址相同。
8>>.lua_setuservalue(index)函数用来将栈顶元素弹出,然后将该元素设置成指定索引位置index处的用户数据的用户值。
9>>.lua_getuservalue(index)函数用来将指定索引位置index处的用户数据关联的用户值压入栈中,并返回该用户值的类型。当不是用户数据或者不存在用户值时就压入空值并返回空类型。

14.lua线程具有以下特性:
1>.lua_State表面意思是lua状态,实际上是lua线程的意思。所以个人认为可以用lua_Thread来代替lua_State。
2>.对lua线程进行操作时,常见的lua c api如下所示:
1>>.luaL_newstate/lua_newstate函数用来创建lua状态以及关联的主线程,然后返回该主线程。
2>>.lua_close函数用来回收主线程以及关联的lua状态。
3>>.lua_newthread(L)函数用来创建一个子线程,然后将子线程压入到父线程L的栈顶,最后返回子线程。具有以下特性:
1>>>.子线程拥有自己独立的栈来存储局部变量。
2>>>.子线程可以共享父线程L的全局变量。
3>>>.子线程一旦不被引用就会被lua自动回收。
4>>.lua_resume(L, from, nargs)函数首先从线程L的独立栈顶开始往下弹出nargs个元素;然后启动处于栈顶的协程体或者唤醒被挂起的协程体,并将弹出的元素按照逆序的方式依次传递给协程体参数列表;最后将线程L的独立栈清空,然后压入函数返回结果。具有以下特性:
1>>>.当协程体执行异常时就会返回LUA_ERRRUN线程状态码,并将异常消息和异常状态码依次压入线程L的独立栈中。
2>>>.当协程体执行结束时就会返回LUA_OK线程状态码,并将协程体的返回值列表依次压入线程L的独立栈中。
3>>>.当协程体执行时被挂起时就会返回LUA_YIELD线程状态码,然后将yield函数的参数列表依次压入线程L的独立栈中。
4>>>.参数 from表示协程从哪个协程中来延续L的。 如果不存在这样一个协程,这个参数可以是 NULL。
5>>>.这里的协程体指的是lua端的函数体。
5>>.lua_yield(L, nresults)函数具有以下特性:
1>>>.协程体中的lua_yield函数被调用时就会挂起线程L;然后从协程体的局部栈顶开始往下弹出nresults个元素;最后将弹出的元素以逆序的方式返回给lua_resume。
2>>>.协程体中的lua_yield函数被唤醒时就会将lua_resume传递给lua_yield的参数列表返回给lua_resume。
3>>>.这里的协程体指的是c端的函数体。
6>>.lua_yieldk(L, nresults, ctx, k)函数具有以下特性:
1>>>.协程体中的lua_yieldk函数被调用时就会挂起线程L;然后从协程体的局部栈顶开始往下弹出nresults个元素;最后将弹出的元素以逆序的方式返回给lua_resume。
2>>>.协程体中的lua_yieldk函数被唤醒时就会调用继承了协程体的局部栈数据的延续函数k;然后从延续函数k的局部栈顶开始往下弹出指定return个数的元素;最后将弹出的元素以逆序的方式返回给lua_resume。
3>>>.这里的协程体指的是c端的函数体。
3>.lua不支持共享内存的抢占式线程。原因有以下几点:
1>>.ISO C中并没有提供这种实现,所以移植到lua中也就相当困难。
2>>.lua作者自己认为共享内存的抢占式线程是一切线程bug的根源,所以在设计lua时就用协程的协作式来代替抢占式,用lua线程的独立栈来代替共享内存。

发布了81 篇原创文章 · 获赞 39 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/zjz520yy/article/details/101678325