29.1 目录迭代器 |
前面我们实现了一个dir函数,给定一个目录作为参数,这个函数以一个table的方式返回目录下所有文件。我们新版本的dir函数将返回一个迭代子,每次调用这个迭代子的时候会返回目录中的一个入口(entry)。按新版本的实现方式,我们可以使用循环来遍历整个目录:
for fname in dir(".") do print(fname) end
在C语言中,我们需要DIR这种结构才能够迭代一个目录。通过opendir才能创建一个DIR的实例,并且必须显式的调用closedir来释放资源。我们以前实现的dir用一个本地变量保存DIR的实例,并且在获取目录中最后一个文件名之后关闭实例。但我们新实现的dir中不能在本地变量中保存DIR的实例,因为有很多个调用都要访问这个值,另外,也不能仅仅在获取目录中最后一个文件名之后关闭目录。如果程序循环过程中中断退出,迭代子根本就不会取得最后一个文件名,所以,为了保证DIR的实例一定能够被释放掉,我们将它的地址保存在一个userdatum中,并使用这个userdatum的__gc的元方法来释放目录结构。
尽管我们实现中userdatum的作用很重要,但这个用来表示一个目录的userdatum,并不需要在Lua可见范围之内。Dir函数返回一个迭代子函数,迭代子函数需要在Lua的可见范围之内。目录可能是迭代子函数的一个upvalue。这样一来,迭代子函数就可以直接访问这个结构[9],但是Lua不可以(也不需要)访问这个结构。
总的来说,我们需要三个C函数。第一,dir函数,一个Lua调用他产生迭代器的工厂,这个函数必须打开DIR结构并将他作为迭代函数的upvalue。第二,我们需要一个迭代函数。第三,__gc元方法,负责关闭DIR结构。一般来说,我们还需要一个额外的函数来进行一些初始的操作,比如为目录创建metatable,并初始化这个metatable。
首先看我们的dir函数:
#include <dirent.h>
#include <errno.h>
/* forward declaration for the iterator function */
static int dir_iter (lua_State *L);
static int l_dir (lua_State *L) {
const char *path = luaL_checkstring(L, 1);
/* create a userdatum to store a DIR address */
DIR **d = (DIR **)lua_newuserdata(L, sizeof(DIR *));
/* set its metatable */
luaL_getmetatable(L, "LuaBook.dir");
lua_setmetatable(L, -2);
/* try to open the given directory */
*d = opendir(path);
if (*d == NULL) /* error opening the directory? */
luaL_error(L, "cannot open %s: %s", path,
strerror(errno));
/* creates and returns the iterator function
(its sole upvalue, the directory userdatum,
is already on the stack top */
lua_pushcclosure(L, dir_iter, 1);
return 1;
}
这儿有一点需要注意的,我们必须在打开目录之前创建userdatum。如果我们先打开目录,然后调用lua_newuserdata会抛出错误,这样我们就无法获取DIR结构。按照正确的顺序,DIR结构一旦被创建,就会立刻和userdatum关联起来;之后不管发生什么,__gc元方法都会自动的释放这个结构。
第二个函数是迭代器:
static int dir_iter (lua_State *L) {
DIR *d = *(DIR **)lua_touserdata(L, lua_upvalueindex(1));
struct dirent *entry;
if ((entry = readdir(d)) != NULL) {
lua_pushstring(L, entry->d_name);
return 1;
}
else return 0; /* no more values to return */
}
__gc元方法用来关闭目录,但有一点需要小心:因为我们在打开目录之前创建userdatum,所以不管opendir的结果是什么,userdatum将来都会被收集。如果opendir失败,将来就没有什么可以关闭的了:
static int dir_gc (lua_State *L) {
DIR *d = *(DIR **)lua_touserdata(L, 1);
if (d) closedir(d);
return 0;
}
最后一个函数打开这个只有一个函数的库:
int luaopen_dir (lua_State *L) {
luaL_newmetatable(L, "LuaBook.dir");
/* set its __gc field */
lua_pushstring(L, "__gc");
lua_pushcfunction(L, dir_gc);
lua_settable(L, -3);
/* register the `dir' function */
lua_pushcfunction(L, l_dir);
lua_setglobal(L, "dir");
return 0;
}
整个例子有一个注意点。开始的时候,dir_gc看起来应该检查他的参数是否是一个目录。否则,一个恶意的使用者可能用其他类型的参数(比如,文件)调用这个函数导致严重的后果。然而,在Lua程序中无法访问这个函数:他被存放在目录的metatable中,Lua程序从来不会访问这些目录。