此第一版为 Lua 5.0 编写。虽然在很大程度上仍然适用于后续版本,但存在一些差异。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
购买本书,您还可以帮助 支持 Lua 项目。
用 Lua 编程 | ||
第四部分。C API 第 29 章。管理资源 |
之前,我们实现了一个 dir
函数,它返回一个表,其中包含给定目录中的所有文件。我们的新实现将返回一个迭代器,每次调用时都会返回一个新条目。借助此新实现,我们将能够使用如下所示的循环遍历目录
for fname in dir(".") do print(fname) end
要在 C 中遍历目录,我们需要一个 DIR
结构。DIR
的实例由 opendir
创建,并且必须通过调用 closedir
显式释放。我们之前实现的 dir
将其 DIR
实例作为局部变量保留,并在检索到最后一个文件名后关闭该实例。我们的新实现无法将此 DIR
实例保留在局部变量中,因为它必须在多次调用中查询此值。此外,它不能仅在检索到最后一个名称后才关闭目录;如果程序中断循环,迭代器将永远无法检索到此最后一个名称。因此,为了确保始终释放 DIR
实例,我们将它的地址存储在用户数据中,并使用此用户数据的 __gc
元方法来释放目录结构。
尽管此表示目录的用户数据在我们实现中扮演着核心角色,但 Lua 不需要看到它。dir
函数返回一个迭代器函数;这是 Lua 所看到的。目录可能是迭代器函数的上值。因此,迭代器函数可以直接访问此结构,但 Lua 代码不能(也不需要)。
总之,我们需要三个 C 函数。首先,我们需要 dir
函数,这是一个工厂,Lua 调用它来创建迭代器;它必须打开一个 DIR
结构,并将其作为迭代器函数的上值。其次,我们需要迭代器函数。第三,我们需要 __gc
元方法,它关闭一个 DIR
结构。和往常一样,我们还需要一个额外的函数来进行初始安排,例如为目录创建一个元表并初始化此元表。
让我们从 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; }这里有一个微妙之处,即我们必须在打开目录之前创建用户数据。如果我们首先打开目录,然后调用
lua_newuserdata
引发错误,我们将丢失 DIR
结构。按照正确的顺序,一旦创建 DIR
结构,它将立即与用户数据关联;无论之后发生什么,__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
元方法关闭一个目录,但它必须采取一个预防措施:因为我们在打开目录之前创建用户数据,所以无论 opendir
的结果如何,这个用户数据都将被收集。如果 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 程序无法访问此函数:它仅存储在目录的元表中,而 Lua 程序永远不会访问这些目录。
版权所有 © 2003–2004 Roberto Ierusalimschy。保留所有权利。 |