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”共享数据的地方。