您的位置:首页 > 编程语言 > Lua

快速掌握Lua 5.3 —— 编写提供给Lua使用的C库函数的技巧 (2)

2016-04-24 17:23 567 查看

Q:什么是”registry”?

A:有时候,我们需要在程序中使用一些非局部的变量。在C中我们可以使用全局变量或是静态变量来实现,而在为Lua编写C库的过程中,使用以上类型的变量并不是一个好的方式。首先,这些变量中无法存储Lua的值。其次,这些变量如果在多个Lua状态机中被使用,则很可能造成非预期的结果。

一个替代方案是,将这些值存储在Lua的全局变量中。这种方式解决了上面提到的两个问题,Lua全局变量可以存储任何Lua的值,同时每一个Lua状态机都有自己独立的一套全局变量。但这依旧不是最好的方式,因为是Lua的全局变量,Lua程序可以随意的修改变量的值,这很可能对C库中的函数在使用这些变量时造成影响。

为了进一步避免上述情况,Lua提供了一张特殊的”table”,它可以供C代码随意使用。但是对于Lua代码,访问却是被禁止的。这个特殊的”table”便是”registry”,

Q:什么是”pseudo-index”?

A:”pseudo-index”类似于虚拟栈中正常的索引,但其与正常索引的区别在于,虽然使用它也是通过虚拟栈,但是其所对应的值并不是存储在虚拟栈中。

LUA_REGISTRYINDEX
就是一个”pseudo-index”,定义在”lua.h”中,它用于通过虚拟栈访问”registry”(但”registry”并非实际存储在虚拟栈中),使用时按照虚拟栈中正常索引的使用方式使用。

例如,你可以通过如下方式获取”registry”中索引为”Key”的元素的值,

lua_pushstring(L, "Key");
lua_gettable(L, LUA_REGISTRYINDEX);


Q:如何保证”registry”中的索引唯一?

A:”registry”就是一个普通的Lua的”table”,因此你可以像访问其他”table”一样使用非
nil
的值作为”key”存取它的元素。然而,由于所有的C库共享相同的”registry”,你必须注意使用什么样的值作为”key”才不会导致命名冲突。

一个防止命名冲突的方法是使用”static”变量的地址作为”key”,这样C链接器就会保证这个地址在所有的库中唯一。

“mylib.c”文件中(
lua_pushlightuserdata
将一个代表C指针的值放到虚拟栈内,之后的章节会详细介绍):

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

static int l_test_registry(lua_State *L)
{
// "key"变量存储什么值不重要,因为用到的是它本身的地址。
static const char key = 'k';
static const char key1 = 'k';

int my_number = 0;

lua_pushlightuserdata(L, (void *)&key);    // 将"key"入栈。
lua_pushnumber(L, 9);    // 将"value"入栈。
lua_settable(L, LUA_REGISTRYINDEX);    // "registry[key] = value"。

// 不同的"key",操作"registry"中不同的元素。
lua_pushlightuserdata(L, (void *)&key1);
lua_pushnumber(L, 10);
lua_settable(L, LUA_REGISTRYINDEX);

// 相同的"key",操作"registry"中相同的元素。
lua_pushlightuserdata(L, (void *)&key);
lua_pushnumber(L, 11);
lua_settable(L, LUA_REGISTRYINDEX);

lua_pushlightuserdata(L, (void *)&key);    // 将"key"入栈。
lua_gettable(L, LUA_REGISTRYINDEX);    // "registry[key]"。
my_number = lua_tonumber(L, -1);    // 获得虚拟栈顶的结果。
printf("%d\n", my_number);    // 11

lua_pushlightuserdata(L, (void *)&key1);
lua_gettable(L, LUA_REGISTRYINDEX);
my_number = lua_tonumber(L, -1);
printf("%d\n", my_number);    // 10

return 0;    // 没有返回值。
}

static const struct luaL_Reg mylib[] = {
{"my_test_registry", l_test_registry},
{NULL, NULL}
};

extern int luaopen_mylib(lua_State* L)
{
luaL_newlib(L, mylib);

return 1;
}


将“mylib.c”编译为动态连接库,

prompt> gcc mylib.c -fPIC -shared -o mylib.so -Wall
prompt> ls
mylib.c    mylib.so    a.lua


“a.lua”文件中:

local mylib = require "mylib"

mylib.my_test_registry()
--[[ results:
11
10
]]


当然你也可以使用字符串作为”registry”的”key”,只要你保证这些字符串唯一。当你打算允许其他的独立库访问你的数据时,字符串的”key”会非常有用(因为其他独立库可以明确的知道”key”的名字)。

最后,不要使用数值作为”registry”的”key”,因为他们是供”reference system”使用的。

Q:什么是”reference system”?

A:”reference system”由一对儿定义在辅助库中的函数组成。使用他们,你可以无需关心如何创建唯一的”key”,便可以在”registry”中自由的存取数据。

/* 生成一个唯一的"key",将栈顶的值出栈作为"value",
* 从虚拟栈的索引"t"处获得"table",之后做相当于"table[key] = value"的操作。
* 函数返回生成的"key"。
* 如果"value"为"nil",则不会生成"key",同时函数返回"LUA_REFNIL"。
* 同时Lua还定义了"LUA_NOREF",它代表一个与"luaL_ref"所返回的任何值都不同的值。
*/
int luaL_ref(lua_State *L, int t);

/* 从虚拟栈的索引"t"处获得"table",之后做相当于"table[ref] = nil"的操作。
* "ref"同时也被释放(可以再次供"luaL_ref"使用)。
* 如果"ref"是"LUA_NOREF"或是"LUA_REFNIL",则函数不做任何操作。
*/
void luaL_unref(lua_State *L, int t, int ref);


luaL_ref
所返回的”key”称之为”reference”。

static int l_test_registry(lua_State *L)
{
int ref = 0, value = 0;

lua_pushinteger(L, 9);    // 数据入栈。
// 将数据存入"registry",并得到数据所对应的"reference"。
ref = luaL_ref(L, LUA_REGISTRYINDEX);
// 从"registry"中获取"reference"所对应的数据,并将其入栈。
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
value = lua_tointeger(L, -1);    // 获取栈顶的数据。
printf("%d\n", value);    // 9
luaL_unref(L, LUA_REGISTRYINDEX, ref);    // 释放"reference"。

return 0;
}


Q:如何在C库函数中实现”Closure”?

A:”registry”为C库函数提供了一种实现全局变量的合理有效的方式,而”Closure”将为C库函数提供一种实现静态变量的合理有效的方式。

与Lua中的”Closure”类似,你可以为C库函数绑定多个”upvalue”,每一个”upvalue”都可以存储一个Lua值。之后当C库函数的这个”Closure”被调用时,它就可以自由的访问它的”upvalues”(通过”pseudo-indices”访问到)。

/* 创建一个C中的"Closure",将栈顶的"n"个值作为其"upvalues"。
* 弹出栈顶作为"upvalues"的"n"个值,最后将"Closure"入栈。
*/
void lua_pushcclosure(lua_State *L, lua_CFunction fn, int n);

// 返回当前运行的函数的第"i"个"upvalue"的"pseudo-indice"。
int lua_upvalueindex(int i);


接下来的例子,我们将在C库函数中重新实现
newCounter
(之前在快速掌握Lua 5.3 —— 函数 的“什么是”Closures”?”部分使用Lua代码实现过)。

“mylib.c”文件中:

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

// "Closure"的函数代码部分。
static int counter(lua_State *L)
{
// 获取"Closure"的第一个"upvalue"。
double val = lua_tonumber(L, lua_upvalueindex(1));
/* 将该"upvalue"加1,然后入栈。
* (这里还没有实际的将"Closure"的"upvalue"更新)
* 该值将作为"Closure"的返回值。
*/
lua_pushnumber(L, ++val);
/* 将新的"upvalue"的值复制一份儿,然后入栈。
* (因为下面"lua_replace"会弹出此值)
* 该值将用于实际更新"Closure"的"upvalue"值。
*/
lua_pushvalue(L, -1);
/* 获取"Closure"的"upvalue",并实际更新它。
* (同时会弹出栈顶用于更新"upvalue"的值)
*/
lua_replace(L, lua_upvalueindex(1));

return 1;    // 返回最新的"upvalue"值。
}

// 创建一个C中的"Closure"。
static int l_newCounter(lua_State *L)
{
lua_pushnumber(L, 0);    // "upvalue"入栈,初始值为0。
// "counter"函数作为"Closure"的函数代码部分,它有一个"upvalue"。
lua_pushcclosure(L, &counter, 1);

return 1;    // 将创建的"Closure"作为返回值返回。
}

static const struct luaL_Reg mylib[] = {
{"my_newCounter", l_newCounter},
{NULL, NULL}
};

extern int luaopen_mylib(lua_State* L)
{
luaL_newlib(L, mylib);

return 1;
}


将“mylib.c”编译为动态连接库,

prompt> gcc mylib.c -fPIC -shared -o mylib.so -Wall
prompt> ls
mylib.c    mylib.so    a.lua


“a.lua”文件中:

local mylib = require "mylib"

newCounter1 = mylib.my_newCounter()
print(newCounter1())    --> 1.0
print(newCounter1())    --> 2.0
newCounter2 = mylib.my_newCounter()
print(newCounter2())    --> 1.0
print(newCounter1())    --> 3.0


附加:

1、Lua API中大部分函数可以接受”pseudo-index”,除了一些会操作虚拟栈本身的函数,例如
lua_remove
lua_insert
等。

2、当使用字符串作为”registry”的”key”时,一些好的习惯可以避免”key”的冲突。比如使用库的名称作为前缀,或是使用”universal unique identifier(uuid)”。很多系统都有专门的工具来产生”uuid”,比如Linux系统下的”uuidgen”。

3、实际上,”reference system”可以应用于任何的”table”,但是他们典型的应用是在”registry”中。

4、可以通过相同的函数代码,绑定不同的”upvalues”,从而创建不同的”Closures”。C库函数中的”Closure”同样如此。

5、C中的”Closure”与Lua中的”Closure”不同之处在于,C中的”Closure”不能共享”upvalues”,每一个”Closure”都有自己独立的”upvalues”。然而,我们可以设置不同”Closure”的”upvalues”指向同一个”table”,这样这个”table”就变成了一个所有”Closure”共享数据的地方。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  lua