首页 > 代码库 > 《Programming in Lua 3》读书笔记(二十四)

《Programming in Lua 3》读书笔记(二十四)

日期:2014.8.8
PartⅣ The C API

28 Techniques for Writing C Functions
官方的API和辅助库都提供了几种帮助创建C函数的机制。在这章,将会介绍数组操控,字符串操控,和在C中存储Lua变量。


28.1 Array Manipulation
     Lua中的数组,就是table在特殊使用情况下的称呼,我们可以使用管理table的方式来管理数组,名叫:lua_settable,lua_gettable.当时,API也为数组提供了一些特殊的函数。提供这些额外函数的一个原因是性能上的考虑:通常在一个算法内部都有一个遍历数组的操作,因此任何提升这个操作的努力都会对整个算法性能的提升产生非常大的影响。另一个原有是便捷性的考虑:跟字符串型key相比,数值key已经足够满足日常需求了。
     API提供了两个额外的函数:
     void lua_rawgeti(lua_State *L,int index,int key);
     void lua_rawseti(lua_State *L,int index,int key);
     这两个函数的意思大致为:index表示table在栈中的位置;key表示table中元素的位置。调用lua_rawgeti(L,t,key) 相当于下面(当t为正值)的这个操作:
     lua_pushnumber(L,key);
     lua_rawget(L,t);
     而调用lua_rawseti(L,t,key)(仍然t为正值)则相当于下面的操作:
     lua_pushnumber(L,key);
     lua_insert(L,-2);
     lua_rawset(L,t);
     两个函数都使用了raw操作,快速。当以数组方式使用table的时候很少使用元方法。
     


28.2 String Manipulation
     当C函数从Lua中接收一个字符串型参数的时候,只需要遵循两个规则:当得到该参数的时候不要从栈中推出该参数;不要修改该参数。
     而当需要在C中创建号字符串再返回给Lua的时候,则需要注意得更多了。需要C代码注意缓存的配置与释放,缓存溢出等。此时,Lua的API就提供了一些函数来帮助处理这些。
     标准库提供了两个字符串的的基本操作:子串的取出和字符串的组合。取出一个子串,只需要记住lua_pushlstring 需要字符串的长度作为额外的第三个参数。因此假如想传递s的位置i至j的子串给lua,需要写的便是:
     lua_pushlstring(L,s+i,j-i+1);
     而Lua也在API中提供了特殊的用来做字符串组合的函数,叫做lua_concat。相当于lua中的 .. 操作符:将number转换成string然后在需要的时候会触发元方法。不仅如此,也可以一次组合两个以上的字符串。调用lua_concat(L,n) 将会组合(且推出)n个栈中顶部的数据,然后将结果push值栈顶。
     而另一个函数是lua_pushfstring:
     const char* lua_pushfstring(lua_State *L,const char *fmt,…);
     这个函数类似于C中的sprintf函数,这个函数根据特定的格式和一些额外的参数来创建一个新的字符串。当然不像sprintf函数,这个函数是不需要提供一个buffer的,Lua会动态的创建一个字符串,其大小按需要调整。函数将组合结果字符串push至栈中,然后返回一个指向该结果的指针。也不需要担心buff的溢出。至Lua5.2,该函数接受以下格式串:
%s     插入一个以0为结尾的字符串
%d     插入一个整数
%f      插入一个Lua的number,即double
%p     插入一个指针
%c     以字符串形式插入一个整数
%%    插入字符%
     当我们只需要处理少量的字符串组合的时候,使用lua_concat 和 lua_pushfstring 是非常有效的。但是当我们需要处理多个字符串的时候,又显得不高效了,此时借助辅助库来使用buff特性能显著提升效率。



28.3 Storing State in C Functions
      通常,C函数需要存储一些非局部数据,即这些数据 outlive their invocation(生存期长于其调用期?)。在C中,通常使用全局或者静态变量来实现这个需求。但是当你使用标准库函数的时候,使用全局和静态变量是不太可取的。首先,不能使用C的变量来存储一个泛型的Lua变量;第二,使用这些变量的库将会在多种Lua state的时候无效。
     Lua的函数有两个地方用来存储非局部数据:全局变量和非局部变量。C API也提供了两个地方来存储非局部数据:registry 和 upvalues。
     registry是一个全局table,只能被C代码访问。特别的是,可以使用这个table来存储数据共享给多个模块,而如果想只为一个模块或为单个函数提供数据,那么需要使用upvalues。

The registry
     registry总是位于pseudo-index,其值是由LUA_REGISTRYINDEX定义的。pseudo-index和栈中的index类似,只不过与之相对应的value并不在栈中。Lua API中接受indices作为参数的函数同样接受pseudo-indices,如lua_remove 、lua_insert 函数。例如从registry中得到一个key为“Key”的值,可以如下操作:
e.g.
lua_getfield(L,LUA_REGISTRYINDEX,"Key");
     registry也就是Lua中的一个table,可以使用Lua中除nil外的所有类型变量来做它的index值。然而,因为C模块中所有的函数公用同一个registry,所以为了避免冲突,需要认真考虑选择合适的作为key的变量类型。字符串类型是作者推荐的。
     而使用数值作为key是不可取的,因为这些key是保留给reference system的。这个系统包含了一组辅助库中的一些函数,这些函数用来允许你存储变量到table中而不需要关心如何创建独特的名字。函数luaL_ref 创建一个新的reference:
e.g.
int r = luaL_ref(L,LUA_REGISTERINDEX);
     调用这个函数将会从栈中推出一个值,然后以一个数值key存储至registry中。称这个key为reference。
     我们使用references主要是当我们需要在一个C的数据结构中存储一个Lua变量的reference。之前我们已经说过,我们不应该在C函数(包含指向Lua字符串的指针)之外存储这些指针。不仅如此,Lua也不提供指向其余对象的指针,如table和函数。因此,我们不能通过指针指向Lua的对象,而只能通过创建reference指向这些然后存储在C中。
     将指向某个值的reference r推进至栈中,执行如下操作:
e.g.
lua_rawgeti(L,LUA_REGISTRYINDEX,r);
     最后,要释放值和reference,需要调用函数luaL_unref:
e.g.
lua_unref(L,LUA_REGISTRYINDEX,r);
     执行上述调用之后,重新调用luaL_ref 可能会再次返回这个reference。
     reference system将nil视为一个特殊情况,无论何时我们以luaL_ref调用一个nil的值,将不会创建一个新的reference,而是返回常量LUA_REFNIL,而对这个操作执行释放动作是无效的:
luaL_unref(L,,LUA_REGISTRYINDEX,LUA_REFNIL);
     而以下操作:
lua_rawgeti(L,LUA_REGISTRYINDEX,LUA_REFNIL)将会push一个nil至栈中。
     也定义了另一个常量LUA_NOREF,这是一个与任何有用的reference不同的数值,用来标记一个reference为不可用。

     而另一个创建key的方式是使用静态变量的地址:C链接器将会保证这些key是独一无二的。这里就需要使用lua_pushlightuserdata来实现这个操作了,使用一个C指针来push一个值至Lua的栈中。下面的例子展现了用这个方法如何向registry存储和使用一个字符串:
e.g.
/* variable with a unique address*/
static char Key = "k";

/* store a string*/
lua_pushlightuserdata(L,(void*)&Key);
lua_pushstring(L,myStr);
lua_settable(L,LUA_REGISTRYINDEX);

/* retrieve a string*/
lua_pushlightuserdata(L,(void*)&Key);
lua_gettable(L,LUA_REGISTRYINDEX);
myStr = lua_tostring(L,-1);

     而lua5.2 为了简化使用变量的地址作为一个唯一key,提供了两个新的函数:lua_rawgeti和lua_reaseti ,只是使用C指针作为key,而不是整数。使用这两个函数可以重写之前的代码:
e.g.
static char Key = "k";

/* store a string*/
lua_pushstring(L,myStr);
lua_rawsetp(L,LUA_REGISTRYINDEX,(void*)&Key);

/* retrieve a string*/
lua_rawgetp(L,LUA_REGISTRYINDEX,(void*)&Key);
myStr = lua_tostring(L,-1);


Upvalues
     与registry存储方式不同,upvalue实现的是类似于C中的静态变量,只在特定函数中可见。每次在Lua中创建一个新的C函数,都可以与之关联任意数量的upvalues;每个upvalue可以存储一个Lua的变量值。之后,当这个函数被调用了,可以使用 pseudo-indices 自由访问这些upvalues。
     称这种带upvalues的C函数为closure,类似于Lua中的closure。但我们可以用同一套C函数代码,不同的upvalues,来创建不同的closure。
     以一个例子做解释:用C创建一个newCounter函数,这个函数是一个工厂,每次访问这个函数都返回一个新的counter函数。尽管是使用同一套代码,但是每次都是一个独立的counter:
e.g.
static int counter(lua_State *L);     /* forward declaration*/
int newCounter(lua_State *L)
{
     lua_pushinteger(L,0);
     lua_pushcclosure(L,&counter,1);
     return 1;
}
     这里的关键函数是lua_pushcclosure ,这个函数用来创建一个新的closure。函数的第二个参数是base function(什么意思?),第三个参数是upvalues的数量。而在创建一个新的closure之前,需要将初始化的upvalues 推进至栈中。在这里的例子中,初始化了一个0 作为closure的upvalues。
     在看一下counter函数的定义:
e.g.
static int counter(lua_State *L)
{
     int val = lua_tointger(L,lua_upvalueindex(1));
     lua_pushinteger(L,++val);     /* new value */
     lua_pushvalue(L,-1);     /* duplicate it */
     lua_replace(L,lua_upvalueindex(1));     /* update upvalue */
     return 1; /* return new value */
}
     在这里关键的函数是lua_upvalueindex ,这个函数产生一个upvalue 的 pseudo-index。特别的,表达式lua_upvalueindex(1) 产生正在运行的函数的第一个upvalue的pseudo-index。这个index和栈中的index类似,只是这些index不是在栈中而已。因此调用lua_tointeger 将会得到第一个upvalue且将其转化为一个number。函数counter 推进一个新的值:++val,得到之前那个number的复本。


Shared upvalues
     通常我们需要在一个库中给所有的函数共享一些变量。之前我们已经可以通过registry来实现,现在我们也可以通过upvalues来实现。
     不像Lua的closure,C的closure不能共享upvalues的。每个closure都有自己私有的upvalues。但是可以通过设置不同的函数来使得upvalues代表同样的一个table,这样这个table就成为了库中所有函数都能共享数据的机制。
     Lua5.2提供了一个函数用来实现上面的这个机制,我们已经使用luaL_newlib 来打开了C的库,而Lua用下面的这个宏来实现刚提到的这个函数:
e.g.
#define luaL_newlib(L,l)                (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
     这个宏就是为库创建了一个新的table,而函数luaL_setfuncs 则将列表l中的函数加入到新的table中。
     而luaL_setfuncs 的第三个参数则代表了库中新函数有多少个upvalues,这些初始化upvalues的值也必须在栈中,即lua_pushcclosure中出现的那些。因此,创建一个库中所有函数共享一个table作为其单一的upvalue的库,可以使用以下代码:
e.g.
/* create library table ('lib' is its list of function) */
luaL_newlibtable(L,lib);
/* create shared upvalue */
lua_newtable(L);
/* add functions in list 'lib' to the new library,sharing previous table as upvalue*/
luaL_setfuncs(L,lib,1);
     最后的这个函数调用:luaL_setfuncs(L,lib,1) 也会将这个共享的table从栈中移除掉,而只将该table应用于新的库中。

《Programming in Lua 3》读书笔记(二十四)