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程序从来不会访问这些目录。


相关链接:
lua程序设计目录 - 中国lua开发者 - lua论坛