Programming in Lua, 2Nd Edition - Chapter 27: Techniques for Writing C Functions

 

 

 

 

Chapter 27: Techniques for Writing C  Functions

 

官方API 和辅助库都提供了一些机制用来帮助C 函数的编写。本章将讨论数组操作,string 操作,和将lua 值存在C 中。

 

27.1 Array Manipulation

 

“数组”只是表在特殊用法下的一个别名。数组操作可以使用lua_settable lua_gettable,但是API 还为数组操作提供了别的函数。提供这些额外函数的原因是性能:在内部循环进行数组访问是常见的操作(例如sorting),所以此操作的任何性能提升对整个算法来说会有很大影响。另一个原因是像string keysinteger keys 需要特别处理。

 

API 提供两个数组操作函数

 

void lua_rawgeti (lua_State *L, int index, int key);

void lua_rawseti (lua_State *L, int index, int key);

 

其中index 是表在栈中的索引,key 是元素在表中的索引

 

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); /* put 'key' below previous value */

lua_rawset(L, t);

 

它们快速,并且数组很少使用元表。


 

以下代码实现map function:用给定的函数作用于数组的每一个元素,并用函数值替换掉原元素

 

main code 中用lua_pcall 而不用lua_call,因为你想要捕获错误。当你写函数,使用lua_call 是一个好主意,如遇到错误它会留给别人处理

 

map function

 

int l_map (lua_State *L) {

     int i, n;

     /* 1st argument must be a table (t) */

     luaL_checktype(L, 1, LUA_TTABLE);

     /* 2nd argument must be a function (f) */

     luaL_checktype(L, 2, LUA_TFUNCTION);

     n = lua_objlen(L, 1); /* get size of table */

     for (i = 1; i <= n; i++) {

         lua_pushvalue(L, 2); /* push f */

         lua_rawgeti(L, 1, i); /* push t[i] */

         lua_call(L, 1, 1); /* call f(t[i]) */

         lua_rawseti(L, 1, i); /* t[i] = result */

     }

     return 0; /* no results */

}

 

测试可以运行lua 解释器,然后用dofile(‘a.lua’) 来运行脚本

 

-- 测试

-- a.lua

function cut(i)

       return i - i % 0.01

end

 

function printTable(t)

       for i, v in ipairs(t) do

              print(v)

       end

end

 

print(my.sin(90))

--> 1

 

t = {math.pi, 2^0.5}

 

printTable(t)

--> 3.1415926535898

--> 1.4142135623731

 

my.map(t, cut)

printTable(t)

-->3.14

-->1.41

 

 

27.2 String Manipulation

 

C 函数从Lua 获得一个string 参数,必须遵守两个准则:不要从栈中弹出这个string,不要修改这个string

 

如果要从C 创建一个string 返回给lua 那么还有更多值得注意的地方。这种情况下buffer 的分配和释放,缓冲区溢出等问题都由C 负责。尽管如此,Lua API 也为这些任务提供了一些函数。

 

标准API 提供了两最基本的串操作函数:子串提取和串连接。lua_pushlstring 接受一个长度作为额外参数。因此,如果你想给lua 传一个string从位置i j的子串,可以这样做

 

lua 传一个从位置i jstring 的子串

 

lua_pushlstring(L, s + i, j - i + 1);

 


 

要以一个给定的分隔符(单个字符)来splits 一个string,并返回一个子串的表。例如,调用split("hi,ho,there",",") 会返回{“hi”, “ho”, “there”}

 

对一个string 分割子串

 

static int l_split (lua_State *L) {

     const char *s = luaL_checkstring(L, 1);

     const char *sep = luaL_checkstring(L, 2);

     const char *e;

     int i = 1;

     lua_newtable(L); /* result */

     /* repeat for each separator */

     while ((e = strchr(s, *sep)) != NULL) {

         lua_pushlstring(L, s, e-s); /* push substring */

         lua_rawseti(L, -2, i++);

         s = e + 1; /* skip separator */

     }

     /* push last substring */

     lua_pushstring(L, s);

     lua_rawseti(L, -2, i);

     return 1; /* return the table */

}

 

t = my.split("hi,ho,there",",")

printTable(t)

--> hi

--> ho

--> there

 

这个函数既不需要额外的buffer,也不限制串的长度。

 

lua_concat 用来连接strings。和Lua 的“ 操作符类似:它将数字转成strings,需要的时侯triggers 元方法。

 

lua_concat(L,n) 连接栈顶的n 个元素,并一一将它们弹出,连接的结果会放在栈顶上。


 

lua_pushfstring 类似C sprintf

 

const char *lua_pushfstring (lua_State *L, const char *fmt, ...);

 

不同于sprintf,这里不需要你提供bufferlua 会动态为你创建string,要多大有多大,也无需担心缓冲区溢出此类问题。此函数将结果string 放栈顶,并返回它的一个指针。此函数不能指定宽度和精度%% for %,“%sfor strings,“%dfor integers,“%ffor lua 数字,既double,“%c for 一个字符或一个整数

 

当我们只想连接少量strings 时,lua_concat lua_pushfstring 都很有用。但是对连接大量stirngs 来说一个接一个的连接效率太低,就像我们在11.6 看到的。我们可以使用由辅助库提供的bufferauxlib 库以两种levels 实现这些buffer。第一level 是类似buffer I/O 操作:它在本地栈中收集小的strings(或单个字符),当buffer 满了就将它们传给Lua(使用lua_pushlstring。第二level 使用lua_concat 和一个栈算法的变体,就是11.6 节我们看到的,连接多个buffer 的结果。


 

下面代码是lstrlib.c string.upper 的实现:

 

str_upper 的实现

 

static int str_upper (lua_State *L) {

     size_t l;

     size_t i;

     luaL_Buffer b;

     const char *s = luaL_checklstring(L, 1, &l);

     luaL_buffinit(L, &b);

     for (i = 0; i < l; i++)

         luaL_addchar(&b, toupper((unsigned char)(s[i])));

     luaL_pushresult(&b);

     return 1;

}

 

使用auxlib buffer,第一步是声明一个luaL_Buffer,并使用LuaL_buffinit 把它初始化。初始化完成后,luaL_Buffer里面存了一个状态 State L 的拷贝,所以当调用操作这个buffer 的其它函数时不需要给它传State L

luaL_addchar将单个字符放入buffer 中。luaL_addlstring 将定长string 入栈,luaL_addstring 将以0 结尾的string 入栈。最后,luaL_pushresult 将最终结果string 放在栈顶,并清空buffer。这些函数的原型如下:

 

void luaL_buffinit (lua_State *L, luaL_Buffer *B);

void luaL_addchar (luaL_Buffer *B, char c);

void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l);

void luaL_addstring (luaL_Buffer *B, const char *s);

void luaL_pushresult (luaL_Buffer *B);

 

使用这些函数不用担心buffer 分配,溢出和其它细节。我们看到连接算法相当高效,str_upper 函数处理巨大strings(大于1 M)没有任何问题

 

使用luaL_Buffer 需要注意一个问题,当你在buffer 中装入东西,它会将一些中间结果保存在Lua 栈上。因此,操作buffer时不要假定栈顶会保持和你操作buffer 之前一样的状态

 

??

luaL_addstring(&b, lua_tostring(L, 1)); /* BAD CODE */

 

安全起见,往buffer 加东西用luaL_addvalue函数

 

void luaL_addvalue (luaL_Buffer *B);

 

当然,如果栈顶的元素不是一个string,或number 则出错。

 


 

27.3 Storing State in C Functions

 

C 中我们常用全局变量或static 变量来保存那些需要长久存在的值。但是当你为lua 编写库函数时,global static 变量不是一个好方法。首先,你不能将lua 的范型值存在C 变量中。其次,使用这些global static 值的库不能被用于multiple Lua states

 

一个Lua 函数有三个地方存放非局部变量:全局变量,函数环境,和non-local variables

 

C API 也提供三个位置来存放非局部数据:registry, environments, upvalues

 

registry 是一个只能由C 代码访问的全局表。registry通常用来存放那些被多个模块共享的数据environments 通常用来存放单个模块的私有数据lua 函数一样,每一个C 函数都有自已的环境表。通常同个模块的所有函数共享同一个环境表,所以它们可以共享数据最后,一个C 函数也以有upvalues,它是lua values,关联到那个特殊函数

 


 

The registry

 

pseudo-index 像栈的索引,但是与之相关联的数据不在栈里面。Lua API 的多数函数接受索引作为参数,同时也接受伪索引(pseudo-index)—— 但是对那些操作栈本身的函数除外,如lua_remove lua_insert。例如,要取得存在registry 里面以“key”为索引的值,可以使用下面的调用:

 

获取registry 的值

 

lua_getfield(L, LUA_REGISTRYINDEX, "Key");

 

registry 是一个普通的表。你可以使用除了nil 外的任意lua 值来索引它。但是因为所有C 模块共享同一个registry,你必须小心选择作为keys 的值,以避免发生碰撞当你想允许其它独立的库访问你的数据,使用string 作为key 特别有用,因为它们只需知道key 的名字

 

string key 名字的选取应避免通常的名字,使用库名为前缀命名是一个好办法。另一个选择是使用唯一标识(uuid),一个uuid 128 位的数字(字串的十六进制表示),由MAC 地址的组合,加上一个时间戮,再加上一个随机组件,这样就保证了不和其它任何uuid 相同

 

不要使用数字作为registry keys,因为这些keys 保留给reference system 使用这个system auxiliary library 中的一堆函数组成,这些函数充许你往一个table 存入一些值,而不用去关心如何创建唯一名字

 

int r = luaL_ref(L, LUA_REGISTRYINDEX);

 

这个调用从栈中弹出一个值,以一个新的整数做为key,将这个值存进registry,并返回这个key。我们称这个key 为一个reference


 

我们通常在这样的情况下使用引用:当需要存储一个引用,这个引用是C 结构内部的一个Lua 值。C 函数获得的指向lua strings 的指针不能被存储在这个函数的外部Lua 甚至不提供指向lua strings 之外的对象的指针,如表或函数指针所以我们不能通过指针来引用一个lua 对象当需要这样的指针时,我们创建一个引用,并存储在C

 

 

将一个与引用r 相关联的值压栈

 

lua_rawgeti(L, LUA_REGISTRYINDEX, r);

 

同时释放引用及其相关联的值

 

luaL_unref(L, LUA_REGISTRYINDEX, r);

 

 

引用系统对nil 有特别的对待。当以nil 值为参数调用luaL_ref,它不会创建一个新引用,而是返回常量LUA_REFNIL

 

这个调用无效

 

luaL_unref(L, LUA_REGISTRYINDEX, LUA_REFNIL);

 

这个调用将一个nil 值入栈

 

lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_REFNIL);

 

引用系统还定义了一个常量LUA_NOREF,它是一个不同于任一引用的整数,可以用它来标记无效引用。任何试图使用LUA_NOREF 来取值的行为都会返回nil并且任何释放LUA_NOREF 的行为都是无效的


 

另一种创建registy keys 的方法是使用static 变量的地址作为keyC 链接器保证这个key 在所有库中是唯一的。这种选择需要lua_pushlightuserdata 函数,它将一个C 指针对应的一个值压入lua 。下面的代码展示了如何使用这种方法从registry存储和取回一个string

 

使用lua_pushlightuserdataregistry存储和取回一个string

 

/* variable with an unique address */

static const char Key = 'k';

/* store a string */

lua_pushlightuserdata(L, (void *)&Key); /* push address */

lua_pushstring(L, myStr); /* push value */

lua_settable(L, LUA_REGISTRYINDEX); /* registry[&Key] = myStr */

/* retrieve a string */

lua_pushlightuserdata(L, (void *)&Key); /* push address */

lua_gettable(L, LUA_REGISTRYINDEX); /* retrieve value */

myStr = lua_tostring(L, -1); /* convert to string */

 

我们将会在28.5 节对light userdata 进行更详细的讨论。

 


 

Environments for C functions

 

因为在lua 5.1每一个我们在lua 注册的C  函数都有自已的环境表。这些函数可以通过一个pseudo-index 访问它的环境,这和通过pseudo-index 访问registry 是一样的。环境的pseudo index LUA_ENVIRONINDEX

 

通常和在模块中使用环境是一样的。我们为模块创建一个表,并让模块中的所有的函数都共享这个表。在C 中设置这样的被共享环境的方法也类似于在Lua 中我们设置这些环境的方法:我们简单地改变main chunk 的环境,所以由它创建的所有函数会自动继承这个新环境。在C 中设置这样一个环境的代码像这样:

 

int luaopen_foo (lua_State *L) {

     lua_newtable(L);

     lua_replace(L, LUA_ENVIRONINDEX);

     luaL_register(L, <libname>, <funclist>);

     ...

}

 

luaopen_foo 函数创建一个新表作为被共享的environment,并且使用lua_replace 来设置这个表作为它自已的environment然后,当它调用luaL_register,所有这里被创建出来的新函数将继承这个当前环境

 

你应该总是使用environment 而不是register,除非你需要和其它模块共享数据特别地,你可以使用引用系统应用环境表,来创建仅对模块可见的引用

 


 

Upvalues

 

register 提供全局变量,environment 提供模块变量,而upvalues实现同等机制C 静态变量,仅在特殊函数内部可见。每次你在lua 中创建一个新的C 函数,你可以用任意数量的upvalues 和这个函数相关联,每个upvalue 可以存一个lua 。稍后,当这个函数被调用,它可以使用pseuso-indices自由访问它的任意upvalues

 

我们称这种C 函数及其upvalues 的结合体为一个C closureC closure Lua closure 类似C closure 的一个有趣事实是,你可以使用相同的函数代码加上不同的upvalues创建不同的闭包

 

看一个简单的例子,让我们创建一个C函数newCounter6.1 节我们用lua 定义了同样的东西)。这个函数是一个工厂:每次被调用都返回一个新的记数器。虽然所有计数器共享同样的C 代码,但它们的每一个都有自已独立的记数。工厂函数像这样:

 

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,它创建一个新闭包。它的第二参数是一个基函数(在这个例子中是记数器),第三参数是upvalues 的个数(此例中是1)。创建一个新闭包之前,我们必须将其upvalues的初始值压入栈中。在我们的例子中,我们为单独的upvalue压入数字0 作为初始值。像预期的那样,lua_pushcclosure 将新闭包放在栈上,所以闭包准备好了作为newCounter 的结果返回。


 

现在,让我们来看counter 的定义:

 

static int counter (lua_State *L) {

     int val = lua_tointeger(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(它实际上是一个宏),它产生一个upvaluepseudo-index这个pseudo-index 像任何栈index 一样,除了它不活在栈中。表达式lua_upvalueindex(1) 是对函数的第一个upvalue 的索引的引用。所以对lua_tointeger 的调用作为一个数取回第一个upvalue 的当前值。然后,计数器函数压入新值++val,拷贝它,并使用一个拷贝来替换upvalue 的值。最后,它返回另一个copy 作为计数器的返回值。

 

作为一个更高级的例子,我们将使用upvalues 实现tuplestuple 是一种带匿名域的常量记录。你可以使用一个数值索引取得一个特定的域,也可以一次获取所有域。在我们的实现中,我们将tuples 表示为函数,这些函数将自已的值存储在它们的upvalues 中。如果以一个数值为参数调用uple 函数,它会返回特定的域,而当以空参调用时,它就返回其所有域。下面的代码演示了tuples 的使用:

 

x = tuple.new(10, "hi", {}, 3)

print(x(1)) --> 10

print(x(2)) --> hi

print(x()) --> 10 hi table: 0x8087878 3

 

C 中,我们将所有tuples表示为同样的t_tuple函数。因为我们既可以传空参也可以传一个数值参数,t_tuple 使用luaL_optint 来获取它的可选参数luaL_optint 函数类似于luaL_checkint不同的地方在于如果缺少参数它会返回一个指定的默认值(此例是0),而不会去抱怨。

 

当我们索引一个不存在的值,结果是一个pseudo-value,它的类型是LUA_TNONE。(栈中也会发生类似情况,如果索引是top 之上)所以,我们的t_tuple 函数使用lua_isnone 来测试它是否有一个指定的upvalue。但是,我们不应该以一个负索引调用lua_upvalueindex,所以当用户提供一个索引时我们必须检查这种情况luaL_argcheck 函数检查一个给定的条件,需要的时侯就引发一个错误。

 

创建tuples 的函数t_new trivial:因为它的参数已经在栈中,它只需要调用lua_pushcclosure来创建一个tuple 的闭包,使用这些参数作为upvalues。最后,数组tuplelib 和函数luaopen_tuple 是用来创建一个tuple 库的标准代码,使用那个单独的new函数。

 


 

Upvalues 仅在特殊函数内部可见的变量

 

---lmylib.c .c

int t_tuple (lua_State *L) {

     int op = luaL_optint(L, 1, 0);

     if (op == 0) { /* no arguments? */

         int i;

         /* push each valid upvalue onto the stack */

         for (i = 1; !lua_isnone(L, lua_upvalueindex(i)); i++)

              lua_pushvalue(L, lua_upvalueindex(i));

         return i - 1; /* number of values in the stack */

     }

     else { /* get field 'op' */

         luaL_argcheck(L, 0 < op, 1, "index out of range");

         if (lua_isnone(L, lua_upvalueindex(op)))

              return 0; /* no such field */

         lua_pushvalue(L, lua_upvalueindex(op));

         return 1;

     }

}

int t_new (lua_State *L) {

     lua_pushcclosure(L, t_tuple, lua_gettop(L));

     return 1;

}

 

static const luaL_Reg mylib[] = {

  {"sin",   my_sin},

  {"dir",   l_dir},

  {"map",   l_map},

  {"split",   l_split},

  {"new", t_new},

  {NULL, NULL}

};

 

LUALIB_API int luaopen_my (lua_State *L) {

  luaL_register(L, LUA_MYLIBNAME, mylib);

  return 1;

}

 

-- a.lua

x = my.new(10, "hi", {}, 3)

print(x(1)) --> 10

print(x(2)) --> hi

print(x()) --> 10 hi table: 0x8087878 3

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值